1. 程式人生 > 實用技巧 >Python網路爬蟲概述(二)

Python網路爬蟲概述(二)

一、正則表示式

正則表示式為我們提供了抓取資料的快捷方式。雖然該正則表示式更容易適應未來變化,但又存在難以構造、可讀性差的問題。當在爬京東網的時候,正則表示式如下圖所示:

利用正則表示式實現對目標資訊的精準採集

此外 ,我們都知道,網頁時常會產生變更,導致網頁中會發生一些微小的佈局變化時,此時也會使得之前寫好的正則表示式無法滿足需求,而且還不太好除錯。當需要匹配的內容有很多的時候,使用正則表示式提取目標資訊會導致程式執行的速度減慢,需要消耗更多記憶體。

二、BeautifulSoup

BeautifulSoup是一個非常流行的 Pyhon 模組。該模組可以解析網頁,並提供定位內容的便捷介面。通過'pip install beautifulsoup4'就可以實現該模組的安裝了。

使用 BeautifulSoup的第一步是將己下載的 HTML 內容解析為 soup文件。

由 於大多 數網 頁都不具備良好的HTML 格式,因此BeautifulSoup需要對實際格式進行確定

BeautifulSoup能夠正確解析缺失的引號並閉合標籤,此外還會新增<html >和<body>標籤使其成為完整的HTML文件。

通常使用find() 和find_all()方法來定位我們需要的元素。

如果你想了解BeautifulSoup全部方法和引數,可以查閱BeautifulSoup的官方文件。雖然BeautifulSoup在程式碼的理解上比正則表示式要複雜一些,但是其更加容易構造和理解。

三、Lxml

Lxml模組使用 C語言編寫,其解析速度比 BeautiflSoup更快,而且其安裝過程也更為複雜,在此小編就不贅述啦。XPath 使用路徑表示式在 XML 文件中選取節點。節點是通過沿著路徑或者 step 來選取的。

Xpath

使用 lxml 模組的第一步和BeautifulSoup一樣,也是將有可能不合法的HTML 解析為 統一格式。

雖然Lxml可以正確解析屬性兩側缺失的引號,並閉合標籤,不過該模組沒有額外新增<html >和<body>標籤

線上複製Xpath表示式可以很方便的複製Xpath表示式。但是通過該方法得到的Xpath表示式放在程式中一般不能用,而且長的沒法看。所以Xpath表示式一般還是要自己親自上手。

四、CSS

CSS選擇器表示選擇元素所使用 的模式。BeautifulSoup整合了CSS選擇器的語法和自身方便使用API。在網路爬蟲的開發過程中,對於熟悉CSS選擇器語法的人,使用CSS選擇器是個非常方便的方法。

CSS選擇器

下面是一些常用的選擇器示例。

  • 選擇所有標籤: *
  • 選擇<a>標 籤: a
  • 選擇所有class=”link” 的元素: .l in k
  • 選擇 class=”link” 的<a>標籤: a.link
  • 選擇 id= " home ” 的<a>標籤: a Jhome
  • 選擇父元素為<a>標籤的所有< span>子標籤: a > span
  • 選擇<a>標籤內部的所有<span>標籤: a span
  • 選擇title屬性為” Home ” 的所有<a>標籤: a [title=Home]

五、效能對比

lxml 和正則表示式模組都是C語言編寫的,而BeautifulSoup則是純Python 編寫的。下表總結了每種抓取方法的優缺點。

相對困難需要注意的是。lxml在內部實現中,實際上是將CSS選擇器轉換為等價的Xpath選擇器。

六、總結

如果你的爬蟲瓶頸是下載網頁,而不是抽取資料的話,那麼使用較慢的方法(如BeautifulSoup) 也不成問題。

如果只需抓取少量資料,並且想要避免額外依賴的話,那麼正則表示式可能更加適合。

不過,通常情況下,

l xml是抓取資料的最好選擇,這是因為該方法既快速又健壯,而正則表示式和BeautifulSoup只在某些特定場景下有用。

Python爬蟲谷歌Chrome F12抓包過程原理解析

瀏覽器開啟網頁的過程就是爬蟲獲取資料的過程,兩者是一樣一樣的。瀏覽器渲染的網頁是豐富多彩的資料集合,而爬蟲得到的是網頁的原始碼htm有時候,我們不能在網頁的html程式碼裡面找到想要的資料,但是瀏覽器開啟的網頁上面卻有這些資料。這就是瀏覽器通過ajax技術非同步載入(偷偷下載)了這些資料

大家禁不住要問:那麼該如何看到瀏覽器偷偷下載的那些資料呢?

答案就是谷歌Chrome瀏覽器的F12快捷鍵,也可以通過滑鼠右鍵選單“檢查”(Inspect)開啟Chrome自帶的開發者工具,開發者工具會出現在瀏覽器網頁的左側或者是下面(可調整),它的樣子就是這樣的:

讓我們簡單瞭解一下它如何使用:

谷歌Chrome抓包:1. 最上面一行選單

左上角箭頭 用來點選檢視網頁的元素

第二個手機、平板圖示是用來模擬移動端顯示網頁

Elements 檢視渲染後的網頁標籤元素

提醒 是渲染後(包括非同步載入的圖片、資料等)的完整網頁的html,不是最初下載的那個html。

Console 檢視JavaScript的console log資訊,寫網頁時比較有用

Sources 顯示網頁原始碼、CSS、JavaScript程式碼

Network 檢視所有載入的請求,對爬蟲很有幫助

後面的暫且不管。

谷歌Chrome抓包:2. 重要區域

圖中紅框的兩個按鈕比較有用,編號為2的是清空請求記錄;編號3的是保持記錄,這在網頁有重定向的時候很有用

圖中綠色區域就是載入完整個網頁,瀏覽器的全部請求記錄,包括網址、狀態、型別等。寫爬蟲時,我們就要在這裡尋找線索,提煉金礦

最下面編號為4的紅框顯示了載入這個網頁,一共請求了181次,數量是多麼地驚人,讓人不禁心疼七瀏覽器來。

點選一條請求的網址,右側就會出現新的視窗顯示該條請求的相信資訊:

圖中左邊紅框就是點選的請求網址;綠框就是詳情視窗。

詳情視窗包括,Headers(請求頭)、Preview(預覽響應)、Response(伺服器響應內容)和Timing(耗時)。

Preview、Response 幫助我們檢視該條請求是不是有爬蟲想要的資料;

Headers幫助我們在爬蟲中重建http請求,以便爬蟲得到和瀏覽器一樣的資料。

瞭解和熟練使用Chrome的開發者工具,大家就如虎添翼可以順利寫出自己的爬蟲啦。

Python爬蟲JSON及JSONPath執行原理詳解

SON(JavaScript Object Notation) 是一種輕量級的資料交換格式,它使得人們很容易的進行閱讀和編寫。同時也方便了機器進行解析和生成。適用於進行資料互動的場景,比如網站前臺與後臺之間的資料互動。

JsonPath 是一種資訊抽取類庫,是從JSON文件中抽取指定資訊的工具,提供多種語言實現版本,包括:Javascript, Python, PHP 和 Java。

JsonPath 對於 JSON 來說,相當於 XPATH 對於 XML。

JsonPath與XPath語法對比:

Json結構清晰,可讀性高,複雜度低,非常容易匹配,下表中對應了XPath的用法。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib2 # json解析庫,對應到lxml import json # json的解析語法,對應到xpath import jsonpath
url
= "http://www.lagou.com/lbs/getAllCitySearchLabels.json" headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36'}
request
= urllib2.Request(url, headers = headers) response = urllib2.urlopen(request)
# 取出json檔案裡的內容,返回的格式是字串 html = response.read() # 把json形式的字串轉換成python形式的Unicode字串 unicodestr = json.loads(html)
# Python形式的列表 city_list = jsonpath.jsonpath(unicodestr, "$..name") #for item in city_list: # print item # dumps()預設中文為ascii編碼格式,ensure_ascii預設為Ture # 禁用ascii編碼格式,返回的Unicode字串,方便使用 array = json.dumps(city_list, ensure_ascii=False) #json.dumps(city_list) #array = json.dumps(city_list) with open("lagoucity.json", "w") as f: f.write(array.encode("utf-8"))

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib2
import json
from lxml import etree
url
= "http://www.qiushibaike.com/8hr/page/2/" headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36'} request = urllib2.Request(url, headers = headers) html = urllib2.urlopen(request).read()
# 響應返回的是字串,解析為HTML DOM模式 text = etree.HTML(html) text = etree.HTML(html)

# 返回所有段子的結點位置,contains()模糊查詢方法,第一個引數是要匹配的標籤,第二個引數是標籤名部分內容 node_list = text.xpath('//div[contains(@id, "qiushi_tag")]')

items
={} for node in node_list: # xpath返回的列表,這個列表就這一個引數,用索引方式取出來,使用者名稱 username = node.xpath('./div/a/@title')[0]
# 取出標籤下的內容,段子內容 content = node.xpath('.//div[@class="content"]/span')[0].text
# 取出標籤裡包含的內容,點贊 zan = node.xpath('.//i')[0].text
# 評論 comments = node.xpath('.//i')[1].text

items
= { "username" : username, "content" : content, "zan" : zan, "comments" : comments } with open("qiushi.json", "a") as f: f.write(json.dumps(items, ensure_ascii=False).encode("utf-8") + " ")

一、python中的str和unicode
一直以來,python中的中文編碼就是一個極為頭大的問題,經常丟擲編碼轉換的異常,python中的str和unicode到底是一個什麼東西呢?
在python中提到unicode,一般指的是unicode物件,例如'哈哈'的unicode物件為u'\u54c8\u54c8'


而str,是一個位元組陣列,這個位元組陣列表示的是對unicode物件編碼(可以是utf-8、gbk、cp936、GB2312)後的儲存的格式。

這裡它僅僅是一個位元組流,沒有其它的含義,如果你想使這個位元組流顯示的內容有意義,就必須用正確的編碼格式,解碼顯示。
例如:

對於unicode物件哈哈進行編碼,編碼成一個utf-8編碼的str-s_utf8,s_utf8就是是一個位元組陣列,存放的就是'\xe5\x93\x88\xe5\x93\x88',但是這僅僅是一個位元組陣列,如果你想將它通過print語句輸出成哈哈,那你就失望了,為什麼呢?

因為print語句它的實現是將要輸出的內容傳送了作業系統,作業系統會根據系統的編碼對輸入的位元組流進行編碼,這就解釋了為什麼utf-8格式的字串“哈哈”,輸出的是“鍝堝搱”,因為 '\xe5\x93\x88\xe5\x93\x88'用GB2312去解釋,其顯示的出來就是“鍝堝搱”。這裡再強調一下,str記錄的是位元組陣列,只是某種編碼的儲存格式,至於輸出到檔案或是打印出來是什麼格式,完全取決於其解碼的編碼將它解碼成什麼樣子。

這裡再對print進行一點補充說明:當將一個unicode物件傳給print時,在內部會將該unicode物件進行一次轉換,轉換成本地的預設編碼(這僅是個人猜測)

二、str和unicode物件的轉換

str和unicode物件的轉換,通過encode和decode實現,具體使用如下:

將GBK'哈哈'轉換成unicode,然後再轉換成UTF8

三、Setdefaultencoding

如上圖的演示程式碼所示:

當把s(gbk字串)直接編碼成utf-8的時候,將丟擲異常,但是通過呼叫如下程式碼:

importsys

reload(sys)

sys.setdefaultencoding('gbk')

後就可以轉換成功,為什麼呢?在python中str和unicode在編碼和解碼過程中,如果將一個str直接編碼成另一種編碼,會先把str解碼成unicode,採用的編碼為預設編碼,一般預設編碼是anscii,所以在上面示例程式碼中第一次轉換的時候會出錯,當設定當前預設編碼為'gbk'後,就不會出錯了。

至於reload(sys)是因為Python2.5初始化後會刪除sys.setdefaultencoding這個方法,我們需要重新載入。

四、操作不同檔案的編碼格式的檔案

建立一個檔案test.txt,檔案格式用ANSI,內容為:

abc中文

用python來讀取

# coding=gbk

print open("Test.txt").read()

結果:abc中文

把檔案格式改成UTF-8:

結果:abc涓枃

顯然,這裡需要解碼:

# coding=gbk

import codecs

print open("Test.txt").read().decode("utf-8")

結果:abc中文

上面的test.txt我是用Editplus來編輯的,但當我用Windows自帶的記事本編輯並存成UTF-8格式時,

執行時報錯:

Traceback (most recent call last):

File "ChineseTest.py", line 3, in

print open("Test.txt").read().decode("utf-8")

UnicodeEncodeError: 'gbk' codec can't encode character u'\ufeff' in position 0: illegal multibyte sequence

原來,某些軟體,如notepad,在儲存一個以UTF-8編碼的檔案時,會在檔案開始的地方插入三個不可見的字元(0xEF 0xBB 0xBF,即BOM)。

因此我們在讀取時需要自己去掉這些字元,python中的codecs module定義了這個常量:

# coding=gbk

import codecs

data = open("Test.txt").read()

if data[:3] == codecs.BOM_UTF8:

data = data[3:]

print data.decode("utf-8")

結果:abc中文

五、檔案的編碼格式和編碼宣告的作用

原始檔的編碼格式對字串的宣告有什麼作用呢?這個問題困擾一直困擾了我好久,現在終於有點眉目了,檔案的編碼格式決定了在該原始檔中宣告的字串的編碼格式,例如:

str='哈哈'

printrepr(str)

a.如果檔案格式為utf-8,則str的值為:'\xe5\x93\x88\xe5\x93\x88'(哈哈的utf-8編碼)

b.如果檔案格式為gbk,則str的值為:'\xb9\xfe\xb9\xfe'(哈哈的gbk編碼)

在第一節已經說過,python中的字串,只是一個位元組陣列,所以當把a情況的str輸出到gbk編碼的控制檯時,就將顯示為亂碼:鍝堝搱;而當把b情況下的str輸出utf-8編碼的控制檯時,也將顯示亂碼的問題,是什麼也沒有,也許'\xb9\xfe\xb9\xfe'用utf-8解碼顯示,就是空白吧。>_<

說完檔案格式,現在來談談編碼宣告的作用吧,每個檔案在最上面的地方,都會用#coding=gbk類似的語句宣告一下編碼,但是這個宣告到底有什麼用呢?到止前為止,我覺得它的作用也就是三個:

  1. 宣告原始檔中將出現非ascii編碼,通常也就是中文;
  2. 在高階的IDE中,IDE會將你的檔案格式儲存成你指定編碼格式。
  3. 決定原始碼中類似於u'哈'這類宣告的將‘哈'解碼成unicode所用的編碼格式,也是一個比較容易讓人迷惑的地方,看示例:

#coding:gbk

ss=u'哈哈'

printrepr(ss)

print'ss:%s'%ss

將這個些程式碼儲存成一個utf-8文字,執行,你認為會輸出什麼呢?大家第一感覺肯定輸出的肯定是:

u'\u54c8\u54c8'

ss:哈哈

但是實際上輸出是:

u'\u935d\u581d\u6431'

ss:鍝堝搱

為什麼會這樣,這時候,就是編碼宣告在作怪了,在執行ss=u'哈哈'的時候,整個過程可以分為以下幾步:

1)獲取'哈哈'的編碼:由檔案編碼格式確定,為'\xe5\x93\x88\xe5\x93\x88'(哈哈的utf-8編碼形式)

2)轉成 unicode編碼的時候,在這個轉換的過程中,對於'\xe5\x93\x88\xe5\x93\x88'的解碼,不是用utf-8解碼,而是用宣告編碼處指定的編碼GBK,將'\xe5\x93\x88\xe5\x93\x88'按GBK解碼,得到就是''鍝堝搱'',這三個字的unicode編碼就是u'\u935d\u581d\u6431',至止可以解釋為什麼printrepr(ss)輸出的是u'\u935d\u581d\u6431' 了。

好了,這裡有點繞,我們來分析下一個示例:

#-*-coding:utf-8-*-

ss=u'哈哈'

printrepr(ss)

print'ss:%s'%ss

將這個示例這次儲存成GBK編碼形式,執行結果,竟然是:

UnicodeDecodeError:'utf8'codeccan'tdecodebyte0xb9inposition0:unexpectedcodebyte

這裡為什麼會有utf8解碼錯誤呢?想想上個示例也明白了,轉換第一步,因為檔案編碼是GBK,得到的是'哈哈'編碼是GBK的編碼'\xb9\xfe\xb9\xfe',當進行第二步,轉換成 unicode的時候,會用UTF8對'\xb9\xfe\xb9\xfe'進行解碼,而大家查utf-8的編碼表會發現,utf8編碼表(關於UTF- 8解釋可參見字元編碼筆記:ASCII、UTF-8、UNICODE)中根本不存在,所以會報上述錯誤。