話不多說,直接進入正題。
一。首先是xpath。
1.官方的來講就是能在xml樹狀結構中尋找節點 xpath用于在xml文檔中通過元素和屬性進行導航和定位。
2.解析網頁,對于一些網頁結構比較明顯的我們一般都采用xpath去進行解析。
3.快速入門
from lxml import etree
# html網頁源代碼
html="""
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
"""
# 將html轉成xml
element=etree.HTML(html)
# print(element)
# 獲取li標簽下面的a標簽的href屬性值
links=element.xpath('//ul/li/a/@href')
print(links) # 列表
# 獲取li標簽下面的a標簽的文本數據
titles=element.xpath('//ul/li/a/text()')
print(titles)
結果如下:
二。BS4
BS4也是一種解析方式,原理基本類似xpath。兩者相對比,xpath需要記住一些語法,bs4則只需要記住一些方法就可以了,掌握Beautifulsoup這個核心類就ok了。
● 安裝
○ pip install lxml
○ pip install bs4 bs4這個庫對我們的lxml存在依賴
詳細使用可以參考中文文檔地址:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
一般常用的find_all和find就能應對大部分情況。
import requests
from bs4 import BeautifulSoup
#解析
soup=BeautifulSoup(text,'html5lib') #這里一般采用 lxml ,也可以使用html5lib
#獲取單個標簽
aa=soup.find('div', id="container")
#獲取標簽下所有的下一級標簽
list=aa.find_all('div', class_="box picblock col3")
三。urllib
保存圖片很好用,其他情況使用不多。
獲取請求示例:
import urllib.request
url='需要訪問的網頁地址'
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
# 1 創建req請求對象并構建headers
req=urllib.request.Request(url,headers=headers)
print(req) # <urllib.request.Request object at 0x0000022D35B9BB88>
# 2 獲取響應數據
response=urllib.request.urlopen(req)
# 3 讀取響應內容
html=response.read().decode('utf-8')
print(html)
保存圖片示例:
是入門理論的最后一部分,完成了這一步就可以說是完成了一個簡單的爬蟲。用來解析響應中的Html文件的Python模塊由很多,解析方式也有所不同。據我觀察beautifulsoup的使用應該比lxml更加普及(我沒有做過系統的調查,這個結果是根據查找到的資料數量來的),但是如何選擇還要看使用者自己的喜好。
beautifulsoup(下稱bs4)使用Python編寫,使用相較于lxml較容易,但是效率也較低。不過bs4支持很多解析器,lxml也是其中一種。
上文說到bs4支持很多解析器,我們在此列出:
-Python自帶的標準庫“html.parser”:因為是內置的庫所以使用簡單,但在Python2.7.3/3.2.2之前容錯能力差
-lxml解析器“lxml”:速度快容錯高,但是需要安裝C語言庫
-lxmlXML解析器“lxml-xml”/“xml”:速度快支持XML,也需要安裝C語言庫
-html5lib解析器“html5lib”:速度慢但會以瀏覽器的形式解析文檔
至于bs4解析HTML文檔只需要小小的一步:
from bs4 import BeautifulSoup
soup_html=BeautifulSoup(你的html文本,"解析器,也就是我剛剛介紹里的粗體部分,也可以不填讓bs4自己選擇")
bs4會將html文本轉換成一個樹形結構,而結構里的對象有四種,分別是Tag(標簽)、NavigableString(字符串)、BeautifulSoup和Comment(注釋)。下面我們分別解釋這四類對象在html文本中的含義。
-Tag:tag的意思就是標簽,它與html文本中的標簽同義。BeautifulSoup對象也可以被看做是一個Tag。Tag中屬性的訪問方法如同字典,比如訪問Tag對象a的href屬性的寫法為a[‘href’]。
-NavigableString:html文本中所有的字符內容(就是除了標簽之外被直接展示出來的文字)都是NavigableString。
-BeautifulSoup:它就是完整的一個html文本,可以被看做一個特殊的Tag。
-Comment:注釋,也可以被當做NavigableString被.string訪問到。
判斷一個子節點對象類型的方法:type(soup_html)==bs4.element.對象名
在將html文本轉換成對象樹之后,bs4會提供一系列方法來查詢它們。下面會列舉出一些常用的方法。
-BeautifulSoup.prettify():得到格式化的標準縮進的html文本。
-Tag.Tagname:用.運算符會返回第一個標簽名為Tagname的標簽。
-Tag.name:返回此標簽的名字(按照這個寫法可能有點多余,但是如果是你不知道標簽名的soup呢?)
-Tag.contents:獲得所有子標簽的列表。
-Tag.parents:獲得所有父標簽的列表。
-Tag.string:獲得標簽內部的文字。(只在標簽內僅有一個NavigableString對象的時候返回值,否則為None)
-Tag.strings:獲得所有文字列表。
-Tag.stripped_strings:獲得去掉換行符和空格的文字列表。
-Tag.Tagname[‘Attrname’]:返回第一個Tagname標簽的Attrname屬性的值。
-Tag.find(attr=”attrname”[,”tagname”]):這個方法查找第一個符合條件的標簽其屬性attr值等于attrname。
-Tag.find_all(attr=”attrname”[,]):同上,不過返回的是所有符合條件的標簽。對于find與find_all函數,當attr為class時需要寫作class_,這是為了避開保留字。
相較于beautifulsoup而言lxml使用C語言編寫(而且beautifulsoup一次要讀取整個DOM),所以在運行效率和速度等方面lxml是當之無愧的優勢。但是lxml采用XPath解析HTML和XML文件,所以如果要用lxml對HTML進行解析還要先學習XPath。
XPath全稱為XML Path Language,也就是XML路徑(解析)語言。顧名思義它最初被設計用于XML文件的搜索,但是現在也支持HTML。
學習XPath的成本(初步運用)應與學習正則表達式類似。(這是個人觀點)
關于XPath的概念等我們限于篇幅就不展示了,請自行尋找資料學習。以下列出的是在爬蟲領域較為常用的語法。
-nodename:選取nodename節點的所有子節點
-/:選取根節點
-//:從當前節點選取任意位置節點
-.:選取當前節點
-..:選取當前節點的父節點
-@:選取屬性
-[]:XPath中的“謂語”,也就是查找條件
-*:匹配任何元素節點
-@*:匹配任何屬性節點
-node():匹配任何節點
-text():獲取文本內容
from lxml import etree
html_lxml=etree.HTML(html) #將html文本變成lxml的對象
html_lxml.xpath("XPath表達式") #用XPath搜索對象
以bs4官網教程里的一段html(愛麗絲文檔)為例,我將寫出這段文本里部分元素節點的XPath以供讀者參考。你也可以使用chrome的開發者工具->copy->copyXPath獲得頁面中標簽的XPath。
義上講,爬蟲只負責抓取,也就是下載網頁。而實際上,爬蟲還要負責從下載的網頁中提取我們想要的數據,即對非結構化的數據(網頁)進行解析提取出結構化的數據(有用數據)。
所以說,網頁下載下來只是第一步,還有重要的一步就是數據提取。不同的爬蟲想要的數據不一樣,提取的數據也就不一樣,但提取方法都是類似的。
最簡單的提取數據的方法,就是使用正則表達式,此種方法簡單,提取的邏輯也不能復雜,不然寫出的正則表達式就晦澀難懂,甚至不能提取復雜的數據結構。
最終,老猿經過多年的使用經驗,選擇了lxml和xpath來解析網頁提取結構化數據。順便說一下 BeautifulSoup,它也是一個很棒的解析HTML的工具,可以使用多個解析器,比如Python標準庫的parser,但是速度比較慢,也可以使用lxml作為解析器,但是它的使用方法、API跟lxml不太一樣。使用下來,還是lxml的API更舒服。
lxml 對C語言庫 libxml2和 libxslt進行綁定,提供了Pythonic的API,它有一些主要特點:
總結為一句話就是,C語言的速度和Python的簡易相結合的神器。
lxml有兩大部分,分別支持XML和HTML的解析:
lxml.etree可以用來解析RSS feed,它就是一個XML格式的文檔。然而爬蟲抓取的絕大部分都是html網頁,所以,我們這里主要講述lxml.html解析網頁的方法。
我們下載得到的網頁就是一串html字符串,如何把它輸入給lxml.html模塊,從而生成html文檔的樹結構呢?
該模塊提供了幾種不同的方法:
下面我們通過具體示例來說明上面幾個方法的不同。
document_fromstring 的使用方法
In [1]: import lxml.html as lh In [2]: z=lh.document_fromstring('<span>abc</span><span>xyz</span>') # 可以看到,它自動加了根節點<html> In [3]: z Out[3]: <Element html at 0x7fc410667b88> In [4]: z.tag Out[4]: 'html' # 還加了<body>節點 In [5]: z.getchildren() Out[5]: [<Element body at 0x7fc4101a3ae8>] # 把字符串的兩個節點放在了<body>里面 In [6]: z.getchildren()[0].getchildren() Out[6]: [<Element span at 0x7fc410092bd8>, <Element span at 0x7fc410667c28>]
fragment_fromstring 的使用
In [11]: z=lh.fragment_fromstring(‘<div>abc</div><div>xyz</div>’) --------------------------------------------------------------------------- ParserError Traceback (most recent call last) <ipython-input-11-a11f9a0f71d1> in <module>() ----> 1 z=lh.fragment_fromstring(‘<div>abc</div><div>xyz</div>’) ~/.virtualenvs/py3.6/lib/python3.6/site-packages/lxml/html/__init__.py in fragment_fromstring(html, create_parent, base_url, parser, **kw) 850 raise etree.ParserError( 851 “Multiple elements found (%s)” --> 852 % ‘, ‘.join([_element_name(e) for e in elements])) 853 el=elements[0] 854 if el.tail and el.tail.strip(): ParserError: Multiple elements found (div, div) # 可以看到,輸入是兩個節點(element)時就會報錯 # 如果加上 create_parent 參數,就沒問題了 In [12]: z=lh.fragment_fromstring('<div>abc</div><div>xyz</div>', create_parent='p') In [13]: z.tag Out[13]: 'p' In [14]: z.getchildren() Out[14]: [<Element div at 0x7fc40a41a818>, <Element div at 0x7fc40a41aea8>]
fragments_fromstring 的使用
# 輸入字符串含有一個節點,則返回包含這一個節點的列表 In [17]: lh.fragments_fromstring('<div>abc</div>') Out[17]: [<Element div at 0x7fc40a124ea8>] # 輸入字符串含有多個節點,則返回包含這多個節點的列表 In [18]: lh.fragments_fromstring('<div>abc</div><div>xyz</div>') Out[18]: [<Element div at 0x7fc40a124b88>, <Element div at 0x7fc40a124f98>]
fromstring 的使用
In [27]: z=lh.fromstring('<div>abc</div><div>xyz</div>') In [28]: z Out[28]: <Element div at 0x7fc40a0eb368> In [29]: z.getchildren() Out[29]: [<Element div at 0x7fc410135548>, <Element div at 0x7fc40a0eb2c8>] In [30]: type(z) Out[30]: lxml.html.HtmlElement
這里,fromstring輸入的如果是多個節點,它會給加一個父節點并返回。但是像html網頁都是從節點開始的,我們使用fromstring() 和 document_fromstring() 都可以得到完整的網頁結構。
從上面代碼中我們可以看到,那幾個函數返回的都是HtmlElement對象,也就是說,我們已經學會了如何從html字符串得到HtmlElement的對象,下一節我們將學習如何操作HtmlElement對象,從中提取我們感興趣的數據。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。