Python數據採集——提取頁面內容的幾種手段

前言

在咱們獲取了網頁的信息後,每每須要對原始信息進行提取,獲得咱們想要的數據。對信息的提取方式主要有如下幾種:正則表達式、XPath、BeautifulSoup。本篇博客主要總結這三種方式的基本語法,以及舉一些例子來講明如何使用這些方法。html

正則表達式

什麼是正則表達式?node

正則表達式是使用某種預約義的模式去匹配一類具備共同特徵的字符串,主要用於處理字符串,能夠快速、準確地完成複雜的查找、替換等要求。python

在Python中,re模塊提供了正則表達式操做所須要的功能。因此,在Python中使用正則表達式須要先import re正則表達式

在使用正則表達式提取信息時能夠歸納爲如下三步(大部分提取信息方法的步驟也是如此):express

  • 尋找規律
  • 使用正則符號表示規律
  • 提取信息

正則表達式的基本符號

這裏主要介紹正則中的基本符號,高級的語法的部分會在後面附上連接供你們參考學習。瀏覽器

  • 通常符號函數

    名稱 描述 示例
    點號. 匹配除換行符\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
  • 邊界匹配spa

    名稱 描述 示例
    hat符號^ 匹配字符串的開頭,在多行模式下匹配每一行的開頭 ^a->ab
    dollar符號$ 匹配字符串的末尾,在多行模式下匹配每一行的末尾 $a->bca
    \b 匹配一個單詞邊界 er\b能夠匹配never可是不能夠匹配verb
    \B 匹配非單詞邊界 er\B能夠匹配verb可是不能夠匹配never
  • 預約義字符集code

    名稱 描述 示例
    \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()<3]’) 選取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:

相關文章
相關標籤/搜索