前言
在我們擷取了網頁的資訊後,往往需要對原始資訊進行提取,得到我們想要的資料。對資訊的提取方式主要有以下幾種:正規表達式、XPath、BeautifulSoup。本篇部落客要總結這三種方式的基本文法,以及舉一些例子來說明如何使用這些方法。
正規表達式
什麼是正規表達式?
正規表達式是使用某種預定義的模式去比對一類具有共同特征的字元串,主要用于處理字元串,可以快速、準确地完成複雜的查找、替換等要求。
在Python中,
re子產品
提供了正規表達式操作所需要的功能。是以,在Python中使用正規表達式需要先
import re
。
在使用正規表達式提取資訊時可以概括為以下三步(大部分提取資訊方法的步驟也是如此):
- 尋找規律
- 使用正則符号表示規律
- 提取資訊
正規表達式的基本符号
這裡主要介紹正則中的基本符号,進階的文法的部分會在後面附上連結供大家參考學習。
- 一般符号
名稱 描述 示例 點号. 比對除換行符
以外任意單個字元,若是要比對\n
則需要使用轉義字元.
\
a.c -> abc, a#c 方括号[] 字元集(字元類)。對應的位置可以是指定字元集中的任意字元,[]中的字元可以逐個列出,也可以給出範圍。^符号表示取反,即除指定字元以外的其他字元。 a[bcd]e -> abe; a[b-f]g -> abg; a[^bc]d -> aefd ad之間不可以出現bc字元 - 數量相關
星号* 星号表示它前面的一個子表達式(普通字元、另一個或幾個正規表達式符号)0次或任意多次 abc* -> ab, abc, abcc 問号? 問号表示它前面的子表達式0次或者1次。 abc? -> ab, abc ; ab?c ->ac, abc 加号+ 加号表示它前面的子表達式1次或者任意多次 abc+ ->abc, abcc, abccc 花括号{m} 比對前一個子表達式m次 ab{3}c -> abbbc 花括号{m, n} 比對前一個子表達式m至n次,m和n可以省略,若省略m,則比對0至n次,若省略n,則比對m至無限次 ab{2,3}c ->abbc, abbbc - 邊界比對
hat符号^ 比對字元串的開頭,在多行模式下比對每一行的開頭 ^a->ab dollar符号$ 比對字元串的末尾,在多行模式下比對每一行的末尾 $a->bca \b 比對一個單詞邊界 er\b可以比對never但是不可以比對verb \B 比對非單詞邊界 er\B可以比對verb但是不可以比對never - 預定義字元集
\d 數字0-9 a\dc->a1c \D 非數字 a\Dc->a#c aec \s 空白字元(空格、\t、\r、\n、\f(換頁)、\v(垂直跳格(垂直制表))) a\sc ->a c \S 非空白字元 a\Sc ->abc, a1c, a#c \w 單詞字元(A-Z,a-z,0-9,_(下劃線)) a\wc ->a0c, abc, a2c \W 非單詞字元 a\Wc ->a c, a#c - 邏輯、分組
| 代表左右表達式任意比對一個。注:它總是先嘗試比對左邊的表達式,一旦成功比對,則跳過右邊的比對 abc|def->abc, def () 被括起來的表達式将作為分組,從表達式左邊開始每遇到一個分組的左括号,編号+1,分組表達式作為一個整體,可以後面接數量詞,通常用于提取内容 (abc){3} ->abcabcabc; a(123|456)->a123c a456c - 複雜一點的用法
.和*共用 . a.*d ->ad,and,amnopqd []和*共用 a[bc]*d ->abd, acd, abbbbd, acbccd
和.*
的差別:.*?
-
:貪婪模式,擷取最長的滿足條件的字元串.*
-
.*?
:非貪婪模式,擷取最短的能滿足條件的字元串
例如:
使用<div> <a>123</a> <a>456</a> </div>
比對出來的結果為:123</a><a>456<a>(.*)</a>
<a>(.*?)</a>
比對出來的結果為:123 和 456
在使用正規表達式提取文本内容時,也常常使用
(最小比對).*?
-
RE子產品的常用方法
使用re子產品時,記得先導入
import re
re.match方法
match(pattern,string[,flags]):
嘗試從字元串的起始位置進行比對,若比對成功,則傳回一個比對的對象,若比對不成功,則傳回none
并且可以使用group(num)或 groups()比對對象函數來擷取比對表達式
>>> import re
>>> print(re.match('www', 'www.cnblog.com'))
<_sre.SRE_Match object; span=(0, 3), match='www'>
>>> print(re.match('com', 'www.cnblog.com'))
None
>>> line = 'Who are you ?.'
>>> macth = re.match(r'(.*) are (.*?) ', line)
>>> macth.group()
'Who are you '
>>> macth.groups()
('Who', 'you')
>>> macth.group(1)
'Who'
>>> macth.group(2)
'you'
re.search方法
search(pattern,string[,flags]):
掃描整個字元串并傳回第一個成功的比對,若比對成功則傳回一個比對的對象,否則傳回None。
>>> print(re.search('www', 'www.cnblog.com'))
<_sre.SRE_Match object; span=(0, 3), match='www'>
>>> print(re.search('cn', 'www.cnblog.com'))
<_sre.SRE_Match object; span=(4, 6), match='cn'>
re.findAll方法
findall(pattern,string[,flags]):
在字元串中找到正規表達式所比對的所有子串,并傳回一個清單,如果沒有找到比對的,則傳回空清單。
>>> line = 'cnblog->123sakuraone456'
>>> print(re.findall(r'\d', line))
['1', '2', '3', '4', '5', '6']
>>> print(re.findall(r'\d+', line))
['123', '456']
>>> print(re.findall(r'\D+', line))
['cnblog->', 'sakuraone']
re.split方法
split(pattern,string[,maxsplit=0]):
按照能夠比對的子串将字元串分割後傳回清單。maxsplit指定分割次數。若是沒有比對的,則不分割。
>>> line = 'www.cnblog.com'
>>> print(re.split(r'\W+', line))
['www', 'cnblog', 'com']
>>> print(re.split(r'\W+', line, 2))
['www', 'cnblog', 'com']
>>> print(re.split(r'\W+', line, 1))
['www', 'cnblog.com']
>>> print(re.split(r'\d+', line, 1))
['www.cnblog.com']
re.sub方法
sub(pattern,repl,string[,count=0]):
将字元串中所有pattern的比對項用repl替換
line = "wodfj1234djsig808"
print(re.sub(r'\D','',line))
1234808
使用XParh
在複雜的文檔結構中去使用正規表達式擷取内容,可能需要花費大量的時間去構造正确的正規表達式。此時我們可能就需要換一種方式提取。
XPath使用路徑表達式來選取XML文檔中的節點或者節點集。這些路徑表達式和我們在正常的電腦檔案系統中看到的表達式非常相似。要擷取一個節點就需要構造它的路徑。
主要在Python中,要使用XPath就需要先安裝一個第三方庫
lxml
節點類型
因為XPath是依靠路徑來選取節點,我們首先就需要知道XPath中的節點類型:
- 元素
- 屬性
- 文本
- 命名空間
- 處理指令
- 注釋
- 文檔節點(根節點)
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>
<bookstore> (文檔節點)
<author>J K. Rowling</author> (元素節點)
lang="en" (屬性節點)
節點之間的關系
XML 文檔是被作為節點樹來對待的,節點之間的關系如下
- 父:bookstore元素是book、title、author、year 以及price元素的父
- 子:book、title、author、year 以及 price 元素都是bookstore元素的子
- 同胞:title、author、year 以及price元素都是同胞
- 先輩:title 元素的先輩是book元素和bookstore
- 後代:bookstore 的後代是book、title、author、year以及price
使用路徑表達式選取節點
表達式 | 示例說明 | ||
---|---|---|---|
nodename | 選取nodename節點的所有子節點 | ||
/ | 從根節點開始選取 | xpath('/div') | 從根節點上選取div節點 |
// | 選取所有的目前節點,不考慮他們的位置 | xpath('//div') | 選取所有div節點 |
. | 選取目前節點 | xpath(‘./div’) | 選取目前節點下的div節點 |
.. | 選取目前節點的父節點 | xpath('..') | 回到上一個節點 |
@ | 選取屬性 | xpath(‘//@calss’) | 選取所有的class屬性 |
XPath謂詞查找特定的節點
謂語被嵌在方括号内,用來查找特定的節點。
結果 | |
---|---|
xpath(‘/body/div[1]’) | 選取body下的第一個div節點 |
xpath(‘/body/div[last()]’) | 選取body下的最後一個div節點 |
xpath(‘/body/div[last()-1]’) | 選取body下的倒數第二個div節點 |
xpath(‘/body/div[positon()❤️]’) | 選取body下的前兩個div節點 |
xpath(‘/body/div[@class]’) | 選取body下帶有class屬性的div節點 |
xpath(‘/body/div[@class=‘main’]’) | 選取body下class屬性是main的div節點 |
xpath(‘/body/div[price>35.00]’) | 選取body下price元素大于35的div節點 |
XPath通配符
通配符 | |||
---|---|---|---|
* | 比對任何元素節點 | xpath(‘/div/*’) | 選取div下的所有子節點 |
@* | 比對任何屬性節點 | xpath(‘/div[@*]’) | 選取所有帶屬性的div節點 |
選取多個路徑的節點
使用 | 運算符可以選取多個路徑
xpath(‘//div丨//table’) | 選取所有div和table節點 |
//book/title丨//book/price | 選取 book 元素的所有 title 和 price 元素 |
/bookstore/book/title丨//price | 選取屬于 bookstore元素的 book 元素的所有 title 元素,以及文檔中所有的 price 元素 |
使用功能函數進行模糊搜尋
函數 | 用法 | 說明 |
---|---|---|
starts-with | xpath(‘//div[starts-with(@id,‘ma’)]’) | 選取id值以ma開頭的div節點 |
contains | xpath(‘//div[contains(@id, ‘ma’)]’) | 選取id值包含ma的div節點 |
and | xpath(‘//div[contains(@id, ‘ma’) and contains(@id,”in”)]’) | 選取id值包含ma和in的div節點 |
text() | xpath(‘//div[contains(text(),‘ma’)]’) | 選取節點文本包含ma的div節點 |
擷取節點的文本内容和屬性值
前面講了那麼多擷取節點的方式,都是為了最終擷取到想要的文本資料做準備。XPath中擷取節點文本資訊使用
text()
,擷取節點的屬性值使用
@屬性
from lxml import etree
import requests
html = requests.get('https://movie.douban.com/top250').content.decode('utf8')
print(html)
selector = etree.HTML(html)
title = selector.xpath('//div[@id="content"]/h1/text()')
print(title) # ['豆瓣電影 Top 250']
link = selector.xpath('//*[@id="content"]/div/div[1]/ol/li[1]/div/div[2]/div[1]/a/@href')
print(link) # ['https://movie.douban.com/subject/1292052/']
如上圖所示,我們使用擷取一個節點文本資訊以及一個節點的屬性值。為了友善我們使用XPath,在浏覽器中的開發者模式下,選中節點,右鍵,就可以Copy我們的想要路徑。不過,這種路徑有時并不是我們想要的,因為隻能擷取到目前這個的節點,是以我們更多時候需要對xpath路徑進行構造。
使用BeautifulSoup
BeautifulSoup4(BS4)是Python的一個第三方庫,用來從HTML和XML中提取資料。BeautifulSoup4在某些方面比XPath易懂,但是不如XPath簡潔,而且由于它是使用Python開發的,是以速度比XPath慢。
使用Beautiful Soup4提取HTML内容,一般要經過以下兩步:
- 處理源代碼生成BeautifulSoup對象
解析器可以使用soup = BeautifulSoup(網頁源代碼, ‘解析器’)
也可以使用html.parser
lxml
- 常使用find_all()、find()和select來查找内容
import requests
from bs4 import BeautifulSoup
html = requests.get('https://movie.douban.com/top250').content.decode('utf8')
print(html)
soup = BeautifulSoup(html, 'lxml')
title = soup.select('#content > h1')[0].text
print(title) # 豆瓣電影 Top 250
print(soup.find('h1').text) # 豆瓣電影 Top 250
link = soup.select('#content > div > div.article > ol > li:nth-child(1) > div > div.info > div.hd > a')[0].get('href')
print(link) # https://movie.douban.com/subject/1292052/
關于BeautifulSoup庫的使用完全可以參考文檔學習,附上中文文檔連結:https://docs.pythontab.com/beautifulsoup4/
小結
花了小半下午整理了對資訊的提取方式。其中,最令我頭疼的還是正規表達式,學習正規表達式已經有好幾遍了,但是在需要使用的時候仍然需要去看手冊。可能這就是一個反複的過程吧。下面附上這三種方式的一些參考學習連結:
正規表達式:
- http://www.runoob.com/python3/python3-reg-expressions.html
- https://www.tutorialspoint.com/python3/python_reg_expressions.htm
XPath:
- http://www.runoob.com/xpath/xpath-intro.html
BeautifulSoup:
- https://docs.pythontab.com/beautifulsoup4/
每天進步一點點,不要停止前進的腳步~