[TOC]
# Web頁面數據解析處理方法
> `urllib3`和`requests`庫都是圍繞支持`HTTP`協議實現`客戶端`功能。但是在對頁面解析處理上他們可能并不能提供更多的幫助,我們需要借助專門的Web頁面數據解析庫來解析并提取出結構化結果數據。
Web頁面的解析方法通常可以用下面三種方法:
- `正則表達式` : 將頁面當做文本處理,簡單直接,大面積撒網。
- `XPath` : `XPath`路徑表達式可以讓我們像訪問目錄一樣訪問Web頁面的所有節點元素, 精準匹配。
- `CSS Selector` : CSS 選擇器`Selector` 與`XPath`相似,以CSS樣式的表達式來定位節點元素, 精準匹配。
## 正則表達式
`Python`中的正則表達式庫`re` 是我們最為常用的正則庫,一條正則表達式可以幫我們過濾掉無用數據,只提取我們需要的格式數據。
在處理網頁時,我們并不會頻繁的全頁面匹配,可想而知這樣的效率極低,通常我們需要縮小數據匹配的范圍節點,在小范圍內進行正則匹配。而縮小范圍的方法常常會結合`XPath`或者`Selector`一起完成,所以要學會三種方法的配合使用。
有時候,可能一條正則表達式就可以提取出所有我們需要的結果,例如下面這個提取`IP:端口`的正則表達式:
```Python
import requests
import re
url = 'https://free-proxy-list.net/anonymous-proxy.html'
r = requests.get(url, timeout=10)
pr_re = r'<td.*?>.*?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*?</td>.*?<td.*?>.*?(\d+).*?</td>'
proxies = re.findall( pr_re, r.text)
proxy_list=[]
for proxy in proxies:
proxy_list.append(':'.join(proxy[0:2]))
print('\n'.join(proxy_list))
```
## `XPath`
> `XPath`路徑表達式可以讓我們像訪問目錄一樣訪問Web頁面的所有節點元素, 精準匹配。
想了解`XPath`的詳細信息,可以閱讀[XPath快速了解](XPath快速了解.md),接下來我們說明下如何在`Python`中使用`XPath`
支持`XPath`的庫有`lxml`、`parsel` ,其實還有很多,只不過這兩個是`API`接口非常好用的庫。
`lxml`是基于C語言開發庫`libxml2`和`libxslt`實現的,因此速度上是非常快的(遠高于Python自帶的`ElementTree`,所以`ElementTree`很少被使用)。并且使用`cssselect`庫擴展支持了`CSS選擇器`接口。
`parsel`則是在`lxml`基礎上的更高級別封裝,并且提供了`XPath`、`CSSSelector`和`re`正則表達式三種提取方式的支持,封裝的接口也是更加簡單易用。同時,`parsel`也是`scrapy`所使用的選擇器。
### lxml`XPath`解析示例
`XPath`表達式有時候我們不知道如何寫時,我們可以通過功瀏覽器的開發者工具幫助獲取`XPath`,具體方法為:
`訪問目標URL` => 按`F12`打開開發者模式 => 選擇`Elements`tab頁 => 右鍵要定位的元素 => 選擇`Copy`中的`Copy XPath`。
如下圖所示:

通過此方法得到的`XPath`,可能很長,或者冗余信息太多,我們只需要在得到的`XPath`表達式上進行優化即可。
#### 示例一:簡單的xpath使用提取博客文章列表
```Python
import requests as req
from lxml import etree
url='https://www.learnhard.cn/feed'
resp = req.get(url)
doc = etree.HTML(resp.content)
items = doc.xpath('//item/title/text()')
print('\n'.join(items))
```
#### 示例二:獲取微博實時熱搜排行榜
> 微博實時熱搜的`cookies`信息需要設置一下,不用登錄。
```Python
import requests as req
from lxml import etree
import re
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
'authority': 'weibo.com',
'cookie': 'UM_distinctid=171437c9856a47-0cd9abe048ffaf-1528110c-1fa400-171437c9857ac3; CNZZDATA1272960323=1824617560-1569466214-%7C1595951362; SCF=AhjAkJNek3wkLok6WSbiibV1WsGffKPYsDlTZtFqiUH_YWL81nk-0xKkiukxpRoDMoIoV0IClwWecgXLLPiBZrw.; SUHB=0gHTlGutSIGq9P; ALF=1628229198; SUB=_2AkMoasG0f8NxqwJRmfoUxG7ibIl_ww7EieKeNjBvJRMxHRl-yT9jqlYktRB6A-rvW2hROYk5DlHgX7r_dk67bcEdfhBN; SUBP=0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWh..ORuiFeK.mEWDWeecX1; SINAGLOBAL=1133654064055.2583.1597394566906; UOR=,,www.comicyu.com; login_sid_t=f855cdd8714fdb25dee824ce5ff8d792; cross_origin_proto=SSL; Ugrow-G0=6fd5dedc9d0f894fec342d051b79679e; TC-V5-G0=4de7df00d4dc12eb0897c97413797808; wb_view_log=1914*10771.0026346445083618; _s_tentry=weibo.com; Apache=4531467438705.743.1597800659782; ULV=1597800659793:3:2:1:4531467438705.743.1597800659782:1597394566920; TC-Page-G0=d6c372d8b8b800aa7fd9c9d95a471b97|1597800912|1597800912; WBStorage=42212210b087ca50|undefined'
}
def main():
url='https://weibo.com/a/hot/realtime'
resp = req.get(url, headers = headers)
doc = etree.HTML(resp.text)
topic_list = doc.xpath('//div[@class="UG_content_row"]')
for topic in topic_list:
desc = topic.xpath('.//div[@class="list_des"]')[0]
topic_title = desc.xpath('h3[@class="list_title_b"]/a/text()')[0].strip()
subinfo = desc.xpath('./div')[0].xpath('string(.)').strip().replace(' ','')
subinfo = re.sub('\s+',',', subinfo)
subinfo = re.findall(r'(.*?),(.*?),.*?([0-9]*?),.*?([0-9]*?),.*?([0-9]+)', subinfo)[0]
print(topic_title + ',' + ','.join(subinfo))
if __name__ == '__main__':
main()
```
運行結果:
```sh
python ./demo_weibo_realtime.py
特朗普反擊奧巴馬夫人,徐子森先生,今天11:39,10,54,11789
...
迪麗熱巴廣州,芒果娛樂,今天21:30,1786,2600,31562
```
### parsel`XPath`解析示例
我們以微博實時熱門關鍵詞為例:
```Python
import requests as req
from parsel import Selector
import re
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
'authority': 'weibo.com',
'cookie': 'UM_distinctid=171437c9856a47-0cd9abe048ffaf-1528110c-1fa400-171437c9857ac3; CNZZDATA1272960323=1824617560-1569466214-%7C1595951362; SCF=AhjAkJNek3wkLok6WSbiibV1WsGffKPYsDlTZtFqiUH_YWL81nk-0xKkiukxpRoDMoIoV0IClwWecgXLLPiBZrw.; SUHB=0gHTlGutSIGq9P; ALF=1628229198; SUB=_2AkMoasG0f8NxqwJRmfoUxG7ibIl_ww7EieKeNjBvJRMxHRl-yT9jqlYktRB6A-rvW2hROYk5DlHgX7r_dk67bcEdfhBN; SUBP=0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWh..ORuiFeK.mEWDWeecX1; SINAGLOBAL=1133654064055.2583.1597394566906; UOR=,,www.comicyu.com; login_sid_t=f855cdd8714fdb25dee824ce5ff8d792; cross_origin_proto=SSL; Ugrow-G0=6fd5dedc9d0f894fec342d051b79679e; TC-V5-G0=4de7df00d4dc12eb0897c97413797808; wb_view_log=1914*10771.0026346445083618; _s_tentry=weibo.com; Apache=4531467438705.743.1597800659782; ULV=1597800659793:3:2:1:4531467438705.743.1597800659782:1597394566920; TC-Page-G0=d6c372d8b8b800aa7fd9c9d95a471b97|1597800912|1597800912; WBStorage=42212210b087ca50|undefined'
}
def main():
url='https://weibo.com/a/hot/realtime'
resp = req.get(url, headers = headers)
# print(resp.text)
selector = Selector(resp.text)
topic_list = selector.xpath('//div[@class="UG_content_row"]')
for topic in topic_list:
desc = topic.xpath('.//div[@class="list_des"]')
topic_title = desc.xpath('h3[@class="list_title_b"]/a/text()').get().strip()
subinfo = desc.xpath('./div').xpath('string(.)').get().strip().replace(' ','')
subinfo = re.sub('\s+',',', subinfo)
subinfo = re.findall(r'(.*?),(.*?),.*?([0-9]*?),.*?([0-9]*?),.*?([0-9]+)', subinfo)[0]
print(topic_title + ',' + ','.join(subinfo))
if __name__ == '__main__':
main()
```
與`lxml`的示例比較可以發現,兩者使用方法非常相近,`parsel`的`xpath()`方法每次返回的都是`SelectorList`對象實例,當需要提取節點值時使用`get()`或者`getall()`方法解析,前者返回單個值,而后者返回一個列表,及時只有一個結果也會返回列表。
## CSSSelector
> `CSS`是`HTML`頁面的樣式描述語言,`CSS選擇器`其實就是用樣式特征來定位元素。
關于`CSS選擇器`詳細語法可以閱讀 [CSS選擇器參考手冊](CSS選擇器參考手冊.md) 這一節。
在你掌握了`CSS選擇器`語法后,接下來就來了解如何在`Python`中使用它。
`Python`中支持`CSS選擇器`的庫包含了`lxml`和`parsel`和`pyquery`,他們內部都是依賴于`cssselect`庫實現。`cssselect`庫原本是`lxml`的一個模塊,后來獨立成為一個項目,但我們依然可以在`lxml.cssselect`中使用它。
支持`CSS選擇器`的庫還有`bs4`, `bs4`依賴 `soupsieve`庫實現`CSS選擇器功能`。
同樣的,我們以示例作為學習參考來了解如何使用`CSS選擇器`。
### lxml中的`CSS選擇器`用法
通過調用`cssselect()`方法使用`CSS選擇器`表達式,如下面示例用于提取博客文章列表信息:
```Python
import requests as req
from lxml import etree
url='https://www.learnhard.cn'
resp = req.get(url)
doc = etree.HTML(resp.content)
title_list = doc.cssselect('article > header > h2 > a')
for item in title_list:
title = item.xpath('string(.)').strip()
url = item.xpath('./@href')[0]
print(f'- [{title}]({url})')
```
### `bs4`中的`CSS選擇器`用法
通過調用`select()`方法使用`CSS選擇器`表達式,如下面示例用于提取博客文章列表信息:
```Python
import requests as req
from bs4 import BeautifulSoup as bs
url='https://www.learnhard.cn'
resp = req.get(url)
soup = bs(resp.content, 'lxml')
item_list = soup.select('article > header > h2 > a')
for item in item_list:
title = item.get_text().strip()
# url = item.attrs['href']
url = item['href']
print(f'- [{title}]({url})')
```
`bs4`可以讓我們訪問一個實例的屬性一樣來訪問標簽元素及其屬性信息,如本例中我們獲取`url`地址時是通過`item.a['href']`獲取當前元素下`<a>`標簽中`@href`屬性值。
### `pyquery`中的`CSS選擇器`用法
```Python
import requests as req
from pyquery import PyQuery as pq
url='https://www.learnhard.cn'
resp = req.get(url)
query = pq(resp.content)
item_list = query('article > header > h2 > a')
for item in item_list:
title = item.text
url = item.attrib['href']
print(f'- [{title}]({url})')
```
### `parsel`中的`CSS選擇器`用法
```Python
import requests as req
from parsel import Selector
url='https://www.learnhard.cn'
resp = req.get(url)
sel = Selector(resp.text)
title_list = sel.css('article > header > h2 > a')
for item in title_list:
title = item.css('::text').get()
url = item.css('::attr("href")').get()
# url = item.attrib['href']
print(f'- [{title}]({url})')
```
看到這里,你會發現`parsel`的文本和屬性被當做(偽)節點處理了,這與其他的處理方式都不同,但是這樣的好處也顯而易見,我們處理屬性和文本變得更加直觀容易了。
關于`CSS選擇器`需要說明的是,多數的`偽類`和`偽元素`選擇器是不支持的,例如`p:first-child` 和 `p::first-line`。雖然如此,支持的`CSS選擇器`已經提供了足夠的功能。`cssselect`的文檔中有詳細說明[Supported selectors](https://cssselect.readthedocs.io/en/latest/#supported-selectors)。
另外`cssselect`支持一些不在`CSS`規范中的選擇器:
- `:contains(text)`偽類選擇器
- `[foo!=bar]`中的`!=`屬性操作符,等同于`:not([foo=bar])`。
- `:scope` 允許訪問選擇器的直接子級, 但是必須放在最開頭位置, `:scope > div::text`
- 命名空間的用法`ns|div`
---
~END~
- 課程大綱
- 入門篇
- 爬蟲是什么
- 為什么要學習爬蟲
- 爬蟲的基本原理
- TCP/IP協議族的基本知識
- HTTP協議基礎知識
- HTML基礎知識
- HTML_DOM基礎知識
- urllib3庫的基本使用
- requests庫的基本使用
- Web頁面數據解析處理方法
- re庫正則表達式的基礎使用
- CSS選擇器參考手冊
- XPath快速了解
- 實戰練習:百度貼吧熱議榜
- 進階篇
- 服務端渲染(CSR)頁面抓取方法
- 客戶端渲染(CSR)頁面抓取方法
- Selenium庫的基本使用
- Selenium庫的高級使用
- Selenium調用JavaScript方法
- Selenium庫的遠程WebDriver
- APP移動端數據抓取基礎知識
- HTTP協議代理抓包分析方法
- Appium測試Android應用基礎環境準備
- Appium爬蟲編寫實戰學習
- Appium的元素相關的方法
- Appium的Device相關操作方法
- Appium的交互操作方法
- 代理池的使用與搭建
- Cookies池的搭建與用法
- 數據持久化-數據庫的基礎操作方法(mysql/redis/mongodb)
- 執行JS之execjs庫使用
- 高級篇
- Scrapy的基本知識
- Scrapy的Spider詳細介紹
- Scrapy的Selector選擇器使用方法
- Scrapy的Item使用方法
- Scrapy的ItemPipeline使用方法
- Scrapy的Shell調試方法
- Scrapy的Proxy設置方法
- Scrapy的Referer填充策略
- Scrapy的服務端部署方法
- Scrapy的分布式爬蟲部署方法
- Headless瀏覽器-pyppeteer基礎知識
- Headless瀏覽器-pyppeteer常用的設置方法
- Headless瀏覽器-反爬應對辦法
- 爬蟲設置技巧-UserAgent設置
- 反爬策略之驗證碼處理方法
- 反爬識別碼之點擊文字圖片的自動識別方法
- 反爬字體處理方法總結
- 防止反爬蟲的設置技巧總結
- 實戰篇
- AJAX接口-CSDN技術博客文章標題爬取
- AJAX接口-拉購網職位搜索爬蟲
- 執行JS示例方法一之動漫圖片地址獲取方法
- JS執行方法示例二完整mangabz漫畫爬蟲示例
- 應用實踐-SOCKS代理池爬蟲
- 落霞小說爬蟲自動制作epub電子書
- 一種簡單的適用于分布式模式知乎用戶信息爬蟲實現示例
- 法律安全說明