【Python數據采集】提取頁面內容的幾種手段


前言

在我們獲取了網頁的信息后,往往需要對原始信息進行提取,得到我們想要的數據。對信息的提取方式主要有以下幾種:正則表達式、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>
      

      使用<a>(.*)</a>匹配出來的結果為:123</a><a>456

      使用<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內容,一般要經過以下兩步:

  1. 處理源代碼生成BeautifulSoup對象

    soup = BeautifulSoup(網頁源代碼, ‘解析器’)
    

    解析器可以使用html.parser也可以使用lxml

  2. 常使用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/

小結

花了小半下午整理了對信息的提取方式。其中,最令我頭疼的還是正則表達式,學習正則表達式已經有好幾遍了,但是在需要使用的時候仍然需要去看手冊。可能這就是一個反復的過程吧。下面附上這三種方式的一些參考學習鏈接:

正則表達式:

XPath:

BeautifulSoup:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM