美麗的湯
BeautifulSoup 可以對 requests 下載下來的 html,css, javascript 進行樹狀節點化分析。
早期曾在 xml解析 一篇中介紹 xml.etree.ElementTree 對xml進行分析。那麼 html 也是屬於 xml 的一種,所以 html 是否也可以使用 ElementTree進行分析呢?答案是不行的,因為 html 內含 javascript,若使用 ElementTree 則會出現例外處理而閃退。
Beautiful Soup能將html結構化成樹狀節點不需多少代碼即可以寫出一個完整的應用程序。Beautiful Soup 自動將輸入文檔轉換為Unicode編碼,輸出文檔轉換為 utf-8 編碼, 所以無需要考慮編碼方式。
在正規表式示裏, 符號多且不好背也不好理解, 讓人非常煩雜, 所以很多人都不喜歡用正規表示式. 此時bs4就派上用場了.
安裝
pip install beautifulsoup4 lxml
若沒有安裝lxml的話, bs4會使用標準庫中的HTML解析器. 不過 lxml 解析器更強大,速度更快,推薦安裝。
使用方式
import requests, re from bs4 import BeautifulSoup page = requests.get("http://www.uuxs.tw/ls/22_22102/") page.encoding="utf-8" soup=BeautifulSoup(page.text, "html.parser") print(soup.prettify()) 結果: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <title> 天降巨富最新章节列表,全文阅读,UU小说网无弹窗广告 </title> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> <meta content="小说天降巨富最新章节列表,全文阅读" name="keywords"/> <meta content="感谢您支持陆原居的小说天降巨富,UU小说网提供天降巨富最新章节连载和在线阅读,更新速度最快且无弹窗广告,7*24小时不间断更新天降巨富,请收藏以方便今后阅读。" name="description"/> ................. <div id="intro"> <p> 作为一个超级富二代装穷是一种什么体验?别拦着我,没有人比我更有资格回答这个问题! </p> <p> 本站提示:各位书友要是觉得《天降巨富》还不错的话请不要忘记向您QQ群和微博里的朋友推荐哦! </p> </div> </div> .........................
上述是使用requests取得網頁page後, 再將page.text傳入 bs4內, 最後使用prettify()將整個結構印出來.
ps : prettify [ˋprɪtɪ͵faɪ] : 美化
本地端檔案
requests的get, post都必需是遠端被在伺服器的檔案. 對於本地端無web server的網頁檔, 則無法開啟. 但bs4確可以輕易的打開, 如下
soup = BeautifulSoup(open('index.htm'))
print(soup.prettify())
請注意, 要開啟的檔案, 必需儲存為ANSI格式, 不可以使用utf-8. 所以請先用記事本重新檢查看看
遍訪整個樹狀結構
bs4會將html的每個標籤轉換成節點, 並以樹狀結構存放在記憶体裏
soup.head : 傳回head節點
soup.title : 傳回title節點
soup.body : 傳回body節點
soup.a : 傳回第一個標籤為<a>的節點
soup.h1 : 傳回第出第一個標籤為<h1>的節點
soup.body.contents : 將 body 內的子節點以list傳回, 可以使用len()查得裏面有幾個節點數
soup.body.children : 將 body 內的子節點以list產生器傳回, 需使用 foreach 列印出來
strings
具有多個節點的物件, 就可以使用strings將所有的子節點的內容(text)印出, 比如soup.body.strings. 此法亦需使用foreach印出
ls=soup.body.strings for l in ls: print(l)
搜尋整顆樹狀結構
soup.a : 列印第一個<a>標籤, 若加string, 則會去除標籤(僅適用於單一節點)
node = soup.find(‘a’, href=’/ls/84_84080/’) : 搜尋第一筆 節點為 <a>, 屬性為 href=’/ls/84_84080/’
node.get(“href”) : 取得節點的 href 屬性
ls=soup.find_all(‘a’, href=’/ls/84_84080/’, recursive=True) : 同上, 但搜尋所有的文件, 並將結果以List傳回
ls=soup.find_all([‘a’, ‘b’], limit=2) : 搜尋多個條件, 並限制傳回的筆數
ls=soup.find_all(“b”,string=”小影后她又奶又萌”) : 搜尋特定節點中,text 為 “小影后她又奶又萌”的節點,結果以list傳回。
底下的代碼,則是利用上述的方法,將天降巨富小說中的文章超連節印出
import requests, re from bs4 import BeautifulSoup page = requests.get("http://www.uuxs.tw/ls/22_22102/") page.encoding="utf-8" soup=BeautifulSoup(page.text, "html.parser") dds=soup.find_all('dd') for node in dds: link=node.find('a') print(node, link.get('href'), link.get('title')) 結果 : <dd><a href="22145254.html" title="第五百五十章 打开后备厢看看">第五百五十章 打开后备厢看看</a></dd> 22145254.html 第五百五十章 打开后备厢看看 <dd><a href="22274451.html" title="第五百五十一章 赵思思何去何从">第五百五十一章 赵思思何去何从</a></dd> 22274451.html 第五百五十一章 赵思思何去何从 <dd><a href="22416022.html" title="第五百五十二章 外婆舅舅这一家">第五百五十二章 外婆舅舅这一家</a></dd> 22416022.html 第五百五十二章 外婆舅舅这一家 <dd><a href="23488023.html" title="第五百五十三章 把亲生母亲接来">第五百五十三章 把亲生母亲接来</a></dd> 23488023.html 第五百五十三章 把亲生母亲接来 <dd><a href="23509205.html" title="第五百五十四章 去别墅接回曹凤">第五百五十四章 去别墅接回曹凤</a></dd> 23509205.html 第五百五十四章 去别墅接回曹凤
正規表示法搜尋
條件內可以擺放正規表示法, 首先必需先import re
import re s='123456789' p=re.compile('[a,5].*') a=p.findall(s) print(a)
“.” : 任何字元
“*” : 至少匹配 0 次
“^” : 整行的開頭
‘^1’ : 1
‘^1.*’ : 123456789
“$” : 整行的結尾
‘[]’ : 匹配到[]內的任一字元
‘[a,5]’ : 5
‘[a,5].*’ : 56789
‘\d’ : 任何數字
‘\D’ : 任何非數字
ls=soup.find_all('a', href=re.compile(("^https:.*")))
節點種類
bs4將html轉成樹狀結構後, 每個節點的種類有四種
Tag, NavigableString, BeautifulSoup, Comment
Tag
就是html中的標籤, 如<head>, <body>, <a href=”xxx”>, 標籤加上裏面的內容, 就形成了Tag
print(soup.title) print(soup.a) 結果 : <title>天降巨富最新章节列表,全文阅读,UU小说网无弹窗广告</title> <a href="#" onclick="this.style.behavior='url(#default#homepage)';this.setHomePage('http://www.uuxs.tw/');">将UU小说网设为首页</a>
上面<a>標籤有上百個, 但使用 soup.a只會抓到第一個
每個標籤都有其屬性, 使用下列方式可取得屬性值
soup.a.name : 傳回節點的標籤名稱, 即 a
soup.a.attrs : 傳回節點中所有屬性的list
soup.a[‘class’] : 傳回屬性為 ‘class’ 的值
soup.a[‘class’]=”newClass’ : 設定新的class名
del soup.a[‘class’] : 刪除屬性名
NavigableString
取得每個標籤的內容, 可以使用
soup.a.string
BeautifulSoup
最上面的根節點, 即為整個文件檔的內容
Comment
comment為註解的意思. 比如
<a class=”pokemon” href=”http://abc.com.tw”><!– Super –> </a>
使用soup.a.string, 則會取出去除註解後的字串 “Super”
若不想把註解印出, 需使用如下方式
if type(soup.a.string)!=bs4.element.Comment: print(soup.a.string)
爬取天降巨富
下列代碼可將天降巨富小說全數爬回
import codecs import os import requests, re from bs4 import BeautifulSoup
url="http://www.uuxs.tw/ls/22_22102/" page = requests.get(url) page.encoding="utf-8" soup=BeautifulSoup(page.text, "html.parser") nodes=soup.find_all('a') links={} for node in nodes: if node.get('title') is not None: links[node.text]=f'{url}/{node.get("href")}' path='c:/thomas/天降巨富' if not os.path.exists(path): os.mkdir(path) for key in links.keys(): filename=os.path.join(path, f'{key}.txt') r=requests.get(links[key]) r.encoding='utf-8' soup=BeautifulSoup(r.text,'html.parser') content=soup.find(id='content') file=codecs.open(filename, 'w', 'utf-8') file.write(content.text) file.close()
補充說明
開啟文字檔方式, 可以使用open(‘檔名’,’模式’, -1), -1表示緩衝大小採預設值, 常用模式如下
r : 只用於讀取
w : 只用於寫入, 會刪除舊檔案
w+ : 用於讀及寫
a : 附加檔尾寫入
file=open(r'd:\pytest.txt','a',-1) file.write("第一行\n") file.write("第二行\n") file.write("第三行\n") file.close()
open()並不處理編碼, 字串是什麼碼, 它就寫入什麼碼. 所以如果想要統一使用utf-8, 就要使用codecs
遞迴列印所有節點的內容
下面的方式, 是練習上述的觀念, 使用自已的方式將每個節點的內容印出來
import requests from bs4 import BeautifulSoup def analysis(nodes): if len(nodes)==1: try: print("attrs: %s : " % (nodes[0].attrs), end="") except: print("error:", end="") print("%s, %s" % (nodes[0].name, nodes[0].string)) else: for n in nodes: if n.name !=None: print("--------------------------%s------------------" % (n.name)) analysis(n.contents) page = requests.get("http://www.uuxs.tw/ls/22_22102/") soup=BeautifulSoup(page.text, "html.parser") analysis(soup.contents)
Yahoo 頭條新聞
Yahoo 新聞超連結都有 class=story-title
, 所以我們只要找出網頁中所有符合此條件的標籤,就可以把頭條新聞的資訊抓出來了。
import requests
from bs4 import BeautifulSoup
page = requests.get('https://tw.yahoo.com/')
page.encoding="utf-8"
if page.status_code == requests.codes.ok:#確認是否下載成功
soup = BeautifulSoup(page.text, 'html.parser')
#ls = soup.find_all('a', class='story-title')
ls = soup.find_all('a')
for l in ls:
print("標題:" + l.text)
print("網址:" + l.get('href'))
else:
print("下載失敗")