比人的文章 今天安裝方式測試了 可行 複製記錄下而已html
1 背景html5
這兩天比較忙,各類鍋鍋接,忙裏偷閒完結這一篇吧。在咱們在上一篇《Python3.X 爬蟲實戰(先爬起來嗨)》中已經介紹了 Python 3 爬蟲的基礎知識,最後也經過了一個不是十分嚴謹的小爬蟲程序展現了其強大的魅力。有人說上一篇《Python3.X 爬蟲實戰(先爬起來嗨)》中有強行安利 python 的嫌疑,是的,名正言順的安利,就是這麼任性,總之這玩意對我來講在不少小工具上獲得了效率的提高,確實好用,也有人問我最初由於什麼機緣接觸的 python,這裏只能說之前作 Android 4.1 Framework 時差分包構建處理那塊 Google 官方使用的是 Pyhton 腳本配合處理的,也算是工做須要被迫學習的吧,只是那時候沒有 get 到 Python 的不少橫向拓展,隨着眼界的拓展,漸漸的就這麼被俘獲了。python
言歸正傳,咱們回到爬蟲話題,上一篇咱們最後總結了一個爬蟲程序的流程,其中有兩個核心的流程就是靜態下載器(我的叫法,對立爲動態網頁下載處理,後面系列文章會介紹)和解析器,天然而然這一篇咱們的核心就是探討這兩大步驟的選型。正則表達式
【工匠若水 http://blog.csdn.net/yanbober 未經容許嚴禁轉載,請尊重做者勞動成果。私信聯繫我】瀏覽器
2 Python3 爬蟲靜態下載器緩存
當咱們經過調度器在 URL管理器中拿到一個 URL 之後要作的第一件事就是交給下載器進行 URL 所在連接的訪問下載,而對於常規的 HTTP WEB 網頁下載通常在短期都能完成(想象下一個網頁在瀏覽器等半天都打不開是一種啥體驗),可是不排除網絡異常、訪問連接非法、WEB 站點服務器異常等狀況,因此要實現一個相對比較健壯的下載器咱們須要考慮的問題還有不少,關於細節邏輯優化和健壯性就得靠本身慢慢優化了。下面咱們主要針對下載器進行一個簡短的技術說明(關於這些 Python3 的模塊詳細用法本身能夠額外學習)。服務器
[該例子完整源碼點我查看]cookie
'''
以下是使用 Python3 內置模塊實現的一個比上一篇稍微健壯一點點的下載器。
經過內置 urllib 進行 header 設置或者代理設置或者啓用會話,支持簡單的 HTTP CODE 5XX 重試機制,支持 GET\POST。
(實際項目考慮和封裝的要比這更加健壯)
'''
from http import cookiejar
from urllib import request, error
from urllib.parse import urlparse網絡
class HtmlDownLoader(object):
def download(self, url, retry_count=3, headers=None, proxy=None, data=None):
if url is None:
return None
try:
req = request.Request(url, headers=headers, data=data)
cookie = cookiejar.CookieJar()
cookie_process = request.HTTPCookieProcessor(cookie)
opener = request.build_opener()
if proxy:
proxies = {urlparse(url).scheme: proxy}
opener.add_handler(request.ProxyHandler(proxies))
content = opener.open(req).read()
except error.URLError as e:
print('HtmlDownLoader download error:', e.reason)
content = None
if retry_count > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
#說明是 HTTPError 錯誤且 HTTP CODE 爲 5XX 範圍說明是服務器錯誤,能夠嘗試再次下載
return self.download(url, retry_count-1, headers, proxy, data)
return content
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[該例子完整源碼點我查看]session
'''
以下是使用 Python3 外部模塊 requests 實現的一個下載器
經過 header 設置或者代理設置、支持會話,支持簡單的重試機制。
(實際項目考慮和封裝的要比這更加健壯,安裝模塊使用命令:pip install requests)
'''
import requests
from requests import Timeout
'''
http://docs.python-requests.org/en/master/
'''
class Downloader(object):
def __init__(self):
self.request_session = requests.session()
self.request_session.proxies
def download(self, url, retry_count=3, headers=None, proxies=None, data=None):
'''
:param url: 準備下載的 URL 連接
:param retry_count: 若是 url 下載失敗重試次數
:param headers: http header={'X':'x', 'X':'x'}
:param proxies: 代理設置 proxies={"https": "http://12.112.122.12:3212"}
:param data: 須要 urlencode(post_data) 的 POST 數據
:return: 網頁內容或者 None
'''
if headers:
self.request_session.headers.update(headers)
try:
if data:
content = self.request_session.post(url, data, proxies=proxies).content
else:
content = self.request_session.get(url, proxies=proxies).content
except (ConnectionError, Timeout) as e:
print('Downloader download ConnectionError or Timeout:' + str(e))
content = None
if retry_count > 0:
self.download(url, retry_count - 1, headers, proxies, data)
except Exception as e:
print('Downloader download Exception:' + str(e))
content = None
return content
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
怎麼樣,經過上面兩段下載器代碼咱們能夠發現通常 Python3 的網絡請求(下載器)要麼使用內部模塊 urllib,要麼使用外部模塊 requests,可是達到的效果都是同樣的,只是一個封裝和便捷的關係。固然,你要是不喜歡這兩個,本身也能夠尋找使用其餘開源的網絡請求模塊,達到目的就行,反正就是一個請求咯。
能夠看到,經過靜態下載器其實拿到的就是 URL 連接對應網站的靜態內容(有些網頁是靜態的,有些是動態的),對於靜態網頁的爬蟲其實咱們這樣經過下載器拿到的數據就夠用了,對於動態網頁咱們後續文章再分析。鑑於此,咱們接下來就該把靜態下載器下載的頁面內容交給解析器處理了。
【工匠若水 http://blog.csdn.net/yanbober 未經容許嚴禁轉載,請尊重做者勞動成果。私信聯繫我】
3 Python3 爬蟲靜態解析器
有了上一部分靜態下載器下載下來的頁面內容,咱們緊接着要乾的事情就是解析內容,也就是在這些頁面中依據本身的規則抓取有價值的數據—–解析器。對於 Python 爬蟲解析經常使用的套路主要有直接正則匹配、BeautifulSoup、LXml這幾種(固然也有別的,只不過經常使用的主流就這幾種),下面咱們分別進行說明。
3-1 正則匹配解析器
顧名思義就是正則表達式匹配查找過濾了,若是你對正則表達式還不熟悉,建議你先看下之前我寫的《正則表達式基礎》一文,而後再來學習 Python3 正則匹配解析器,額,實質就是 Python 字符串正則匹配咯,再通俗點就是 Python 的 re 模塊啦,在爬蟲裏使用 re 咱們要注意以下幾個套路:
在使用 Python re 正則模塊時建議你們給正則字符串保持常加 r 前綴的習慣,避免由於轉義帶來坑爹的鍋,由於正則原本就十分靈活,複雜一點就十分晦澀。
當咱們使用 re.compile(exp_str) 方法時因爲 re 內部會編譯 exp_str 正則表達式是否合法,而後用編譯過的表達式去匹配,而爬蟲通常都是依據一個指定的正則表達式對成百上千的頁面進行循環匹配,因此爲了效率儘可能將 re.compile(exp_str) 方法緩存起來,總之避免屢次調用一樣的,避免效率問題。
如《正則表達式基礎》一文所示,儘可能編寫非貪婪模式的正則,默認是貪婪匹配的。
分組匹配輸出時 re 的 group(x) 方法套路要謹防,group(0) 是原始字符串,group(1)、group(2) ……纔是第 一、二、……個分組子串,切記套路。
編寫正則時注意 re 的 compile(pattern, flags=0) 第二個參數含義,謹防套路,譬如咱們想讓 ‘.’ 在 DOTALL 模式下也能匹配 ‘\n’ ,就得注意將 flags 設置爲 re.S 等。
若是你看了《正則表達式基礎》一文明白了正則表達式但不會用 Python 的 re 模塊的話建議再看看網上的 Python正則表達式指南。
不 BB 了,咱們來看一個經過下載器下載下來靜態頁面內容後交給正則解析器處理的例子吧,下面是抓取解析 CSDN 個人博客評論管理列表中每一個 item 的文章名字article、文章連接url、評論人名字commentator、評論時間time、評論內容content,而後生成一個字典列表保存解析的數據,要解析的網頁內容以下:
這裏寫圖片描述
解析器代碼以下 [該例子完整源碼點我查看]:
def get_page_feedback_dict(self, page_index=1):
'''
獲取CSDN個人博客頁面的評論管理頁面我文章的評論列表(按照評論頁數獲取)
:return: {'maxPage'100:, 'dict':[{'article':'xxx', 'url':'xxx', 'commentator':'xxx', 'time':'xxx', 'content':'xxx'}]}
'''
content = self.opener.open(self.url_feedback+str(page_index)).read().decode("utf-8")
print(content)
max_page = re.search(re.compile(r'<div class="page_nav"><span>.*?共(\d+)頁</span>'), content).group(1)
reg_main = re.compile(r"<tr class='altitem'>.*?<a href='(.*?)'.*?>(.*?)</a></td><td><a.*?class='user_name' target=_blank>(.*?)</a></td><td>(.*?)</td>.*?<div class='recon'>(.*?)</div></td></tr>", re.S)
main_items = re.findall(reg_main, content)
dict_list = list()
for item in main_items:
dict_list.append({
'url': item[0],
'article': item[1],
'commentator': item[2],
'time': item[3],
'content': item[4]
})
print(str(dict_list))
return {'maxPage': max_page, 'dict': dict_list}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
獲取到的 dict_list 解析後字典列表以下:
[
{
'url': 'http://blog.csdn.net/yanbober/article/details/73162298#comments',
'article': 'Python3.X 爬蟲實戰(先爬起來嗨)',
'commentator': 'yanbober',
'time': '2017-06-14 14:24',
'content': '[reply]qq_39168495[/reply]<br>機器人咯'
},
{
'url': 'http://blog.csdn.net/yanbober/article/details/73162298#comments',
'article': 'Python3.X 爬蟲實戰(先爬起來嗨)',
'commentator': 'yanbober',
'time': '2017-06-14 14:24',
'content': 'XXXXXXXXXXXX'
},
......
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如上就是一個經過 Python re 正則表達式編寫的爬蟲解析器,固然,這個不夠健壯,實質須要將解析出來的數據再進行清洗使用,這裏再也不過多說明,不過能夠看到直接使用正則匹配解析的代碼是比較晦澀的,除太小型的爬蟲之外不建議採用。
3-2 BeautifulSoup4 解析器
BB 完正則匹配解析器咱們就能夠長舒一口氣了,畢竟大清都滅亡了,咱們也要拋棄石器時代的解析器,擁抱 21 世紀的 BeautifulSoup4 解析器,關於這個外部神器模塊咱們能夠參考官方網站或者官方中文文檔學習。
安裝該外部模塊直接命令行執行:pip install beautifulsoup4
BeautifulSoup4 是一個工具箱,經過它解析文檔能夠爲咱們十分簡單的提供須要抓取的數據;它自動會將咱們輸入的文檔轉換爲 Unicode 編碼,輸出時轉換爲 UTF-8 編碼,咱們不用考慮操蛋的文本解析編碼方式(除非文檔沒有指定編碼方式,這種狀況下 BeautifulSoup4 就無法自動識別編碼方式了,咱們須要主動說明下 WEB 頁面原始編碼方式就好了)。
BeautifulSoup4 除過支持 Python 標準庫中的 HTML 解析器外還支持一些第三方解析器,譬如 LXml、html5lib 等(注意:設置不一樣解析器對於錯誤格式 WEB 頁面解析可能會獲得不同的結果),想要使用這些第三方解析器就得本身先安裝好,安裝命令以下:
pip install lxml
pip install html5lib
1
2
1
2
不過依然推薦給 BeautifulSoup4 使用 LXml 做爲解析器(解析效率高),下表列出了官方文檔中主要的解析器優缺點(圖片來自官方文檔):
這裏寫圖片描述
光說不練假把式,下面給出一個解析知乎登陸頁面 FORM 表單中的 _xsrf 和 captcha 連接供登陸使用,下載器下載下來的待解析知乎登陸界面以下:
這裏寫圖片描述
解析代碼以下[該例子完整源碼點我查看]:
def get_login_xsrf_and_captcha(self):
try:
url_login = "https://www.zhihu.com/#signin"
url_captcha = 'http://www.zhihu.com/captcha.gif?r=%d&type=login&lang=cn' % (time.time() * 1000)
login_content = self.request_session.get(url_login).content
soup = BeautifulSoup(login_content, 'lxml')
#find 方法第二個參數還能夠是 python 編譯的正則表達式
#譬如soup.find_all("a", href=re.compile(r"/item/\w+"))
xsrf = soup.find('input', attrs={'name': '_xsrf'})['value']
captcha_content = self.request_session.get(url_captcha).content
return {'xsrf': xsrf, 'captcha_content': captcha_content}
except Exception as e:
print('get login xsrf and captcha failed!'+str(e))
return dict()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
怎麼樣,比起正則匹配是否是可讀性好了不少,至少不那麼晦澀難懂和容易坑本身了,並且效率還比正則高,有沒有瞬間感受從石器時代到了智能時代;對於 BeautifulSoup4 工具包提供的函數不熟悉不要緊,本身記得經常查閱他們官方中文文檔就好了,你要感受到慶幸,他們文檔是十分精煉簡潔的。
3-3 LXml 解析器
進入智能時代之後還有個更牛逼的解析器 ——– LXml,名副其實的屌炸天,關於它能夠參見官方文檔,這貨使用 C 語言編寫,解析速度比 BeautifulSoup 更快;上面已經介紹了把 LXml 做爲 BeautifulSoup 內置解析器的 BeautifulSoup 用法,信用卡催收系統這裏咱們直接給出一個用 LXml 使用 XPath 選擇器和內置方法的用法實戰說明這個靈活牛叉的解析器,關於細節基礎知識不在本系列討論範圍以內,可查看參閱官方文檔等。
咱們以爬取 https://www.meitulu.com/ 美圖錄網站爲例說明,首先要解析的就是主頁的推薦模特列表點擊跳轉的二級連接(下面的 parse_main_subjects 函數,也即下圖中 class=」img」 的 ul 中 li 下的 a 標籤的 href 連接)以下:
這裏寫圖片描述
接着解析進入二級頁面(模特大圖列表頁,其頁面第一頁爲DDD.html、其餘頁規則爲 DDD_index.html),咱們解析了這個模特的名字和總共具有多少張照片,而後一頁一頁解析他們的高清大圖下載連接。
解析代碼以下[該例子完整源碼點我查看]:
class HtmlParser(object):
def parse_main_subjects(self, content):
'''
解析美圖錄網站主頁模特分類頁面連接
:param content: 美圖錄主頁內容
:return: ['一個模特的大圖頁面', '一個模特的大圖頁面']
'''
html = etree.HTML(content.lower())
subject = html.xpath('//ul[@class="img"]/li')
subject_urls = list()
for sub in subject:
a_href = sub[0].get('href')
subject_urls.append(a_href)
return subject_urls
def parse_subject_mj_info(self, content):
'''
獲取具體模特大圖頁面開頭的模特信息
:param content: 一個類別的模特頁面內容
:return: {'count': 該模特具有圖總數, 'mj_name': 模特名字}
'''
html = etree.HTML(content.lower())
div_cl = html.xpath('//div[@class="c_l"]')
pic_count = re.search(re.compile(r'.*?(\d+).*?'), div_cl[0][2].text).group(1)
return {'count': pic_count, 'mj_name': div_cl[0][4].text}
def parse_page_pics(self, content):
'''
獲取一個模特頁面的模特大圖下載連接
:param content: 一個類別的模特頁面內容
:return: ['大圖連接', '大圖連接']
'''
html = etree.HTML(content.lower())
return html.xpath('//div[@class="content"]/center/img/@src')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[該例子完整源碼點我查看],其解析器徹底使用了 LXml 和 XPath 語法,它就會幫咱們從美圖錄網站主頁進去挨個推薦模特二級頁面依次自動爬取大圖(只爬高清大圖)下載,log 以下:
這裏寫圖片描述
生成的爬取資源以下(依據模特名字命名目錄存起來,已經爬取下載過的就不下載了):
這裏寫圖片描述
若是看了上面例子仍是搞不懂 LXml 的話能夠建議你先看下網絡上的Python lxml教程一文,而後再去看看官方文檔就明白了,不過仍是一句話,多練便可,實戰幾把你就秒懂了。
【工匠若水 http://blog.csdn.net/yanbober 未經容許嚴禁轉載,請尊重做者勞動成果。私信聯繫我】
4 總結
這一篇內容主要延續上一篇《Python3.X 爬蟲實戰(先爬起來嗨)》,重點偏向於爬蟲爬取靜態頁面的下載器與解析器經常使用套路引導,主要適用於理解爬蟲流程和本身編寫小爬蟲程序,對於大型爬蟲這些介紹是十分不健壯的,咱們通常會採用第三方爬蟲框架,對於框架和動態頁面爬取咱們後面系列會進行介紹的。