PySpider 是一個我我的認爲很是方便而且功能強大的爬蟲框架,支持多線程爬取、JS動態解析,提供了可操做界面、出錯重試、定時爬取等等的功能,使用很是人性化。css
本篇內容經過跟我作一個好玩的 PySpider 項目,來理解 PySpider 的運行流程。html
具體的安裝過程請查看本節講述python
安裝git
嗯,安裝好了以後就與我大幹一番吧。github
我以前寫過的一篇文章web
抓取淘寶MM照片json
因爲網頁改版,爬取過程當中須要的 URL 須要 JS 動態解析生成,因此以前用的 urllib2 不能繼續使用了,在這裏咱們利用 PySpider 從新實現一下。數組
因此如今咱們須要作的是抓取淘寶MM的我的信息和圖片存儲到本地。瀏覽器
爬取目標網站:https://mm.taobao.com/json/request_top_list.htm?page=1,你們打開以後能夠看到許多淘寶MM的列表。多線程
列表有多少?
https://mm.taobao.com/json/request_top_list.htm?page=10000,第10000頁都有,看你想要多少。我什麼也不知道。
隨機點擊一位 MM 的姓名,能夠看到她的基本資料。
能夠看到圖中有一個個性域名,咱們複製到瀏覽器打開。mm.taobao.com/tyy6160
嗯,往下拖,海量的 MM 圖片都在這裏了,怎麼辦你懂得,咱們要把她們的照片和我的信息都存下來。
P.S. 注意圖中進度條!你猜有多少圖片~
安裝成功以後,跟我一步步地完成一個網站的抓取,你就會明白 PySpider 的基本用法了。
命令行下執行
1
|
pyspider all
|
這句命令的意思是,運行 pyspider 並 啓動它的全部組件。
能夠發現程序已經正常啓動,並在 5000 這個端口運行。
接下來在瀏覽器中輸入 http://localhost:5000,能夠看到 PySpider 的主界面,點擊右下角的 Create,命名爲 taobaomm,固然名稱你能夠隨意取,繼續點擊 Create。
這樣咱們會進入到一個爬取操做的頁面。
整個頁面分爲兩欄,左邊是爬取頁面預覽區域,右邊是代碼編寫區域。下面對區塊進行說明:
左側綠色區域:這個請求對應的 JSON 變量,在 PySpider 中,其實每一個請求都有與之對應的 JSON 變量,包括回調函數,方法名,請求連接,請求數據等等。
綠色區域右上角Run:點擊右上角的 run 按鈕,就會執行這個請求,能夠在左邊的白色區域出現請求的結果。
左側 enable css selector helper: 抓取頁面以後,點擊此按鈕,能夠方便地獲取頁面中某個元素的 CSS 選擇器。
左側 web: 即抓取的頁面的實時預覽圖。
左側 html: 抓取頁面的 HTML 代碼。
左側 follows: 若是當前抓取方法中又新建了爬取請求,那麼接下來的請求就會出如今 follows 裏。
左側 messages: 爬取過程當中輸出的一些信息。
右側代碼區域: 你能夠在右側區域書寫代碼,並點擊右上角的 Save 按鈕保存。
右側 WebDAV Mode: 打開調試模式,左側最大化,便於觀察調試。
依然是上一節的那個網址,https://mm.taobao.com/json/request_top_list.htm?page=1,其中 page 參數表明頁碼。因此咱們暫時抓取前 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
31
|
from pyspider.libs.base_handler import *
class Handler(BaseHandler):
crawl_config = {
}
def __init__(self):
self.base_url = 'https://mm.taobao.com/json/request_top_list.htm?page='
self.page_num = 1
self.total_num = 30
@every(minutes=24 * 60)
def on_start(self):
while self.page_num <= self.total_num:
url = self.base_url + str(self.page_num)
print url
self.crawl(url, callback=self.index_page)
self.page_num += 1
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('a[href^="http"]').items():
self.crawl(each.attr.href, callback=self.detail_page)
@config(priority=2)
def detail_page(self, response):
return {
"url": response.url,
"title": response.doc('title').text(),
}
|
點擊 save 保存代碼,而後點擊左邊的 run,運行代碼。
運行後咱們會發現 follows 出現了 30 這個數字,說明咱們接下來有 30 個新請求,點擊可查看全部爬取列表。另外控制檯也有輸出,將全部要爬取的 URL 打印了出來。
而後咱們點擊左側任意一個綠色箭頭,能夠繼續爬取這個頁面。例如點擊第一個 URL,來爬取這個 URL
點擊以後,再查看下方的 web 頁面,能夠預覽實時頁面,這個頁面被咱們爬取了下來,而且回調到 index_page 函數來處理,目前 index_page 函數咱們尚未處理,因此是繼續構件了全部的連接請求。
好,接下來咱們怎麼辦?固然是進入到 MM 到我的頁面去爬取了。
爬取到了 MM 的列表,接下來就要進入到 MM 詳情頁了,修改 index_page 方法。
1
2
3
|
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page)
|
其中 response 就是剛纔爬取的列表頁,response 其實就至關於列表頁的 html 代碼,利用 doc 函數,實際上是調用了 PyQuery,用 CSS 選擇器獲得每個MM的連接,而後從新發起新的請求。
好比,咱們這裏拿到的 each.attr.href 多是 mm.taobao.com/self/model_card.htm?user_id=687471686,在這裏繼續調用了 crawl 方法,表明繼續抓取這個連接的詳情。
1
|
self.crawl(each.attr.href, callback=self.detail_page)
|
而後回調函數就是 detail_page,爬取的結果會做爲 response 變量傳過去。detail_page 接到這個變量繼續下面的分析。
好,咱們繼續點擊 run 按鈕,開始下一個頁面的爬取。獲得的結果是這樣的。
哦,有些頁面沒有加載出來,這是爲何?
在以前的文章說過,這個頁面比較特殊,右邊的頁面使用 JS 渲染生成的,而普通的抓取是不能獲得 JS 渲染後的頁面的,這可麻煩了。
然而,幸運的是,PySpider 提供了動態解析 JS 的機制。
友情提示:可能有的小夥伴不知道 PhantomJS,能夠參考
由於咱們在前面裝好了 PhantomJS,因此,這時候就輪到它來出場了。在最開始運行 PySpider 的時候,使用了pyspider all
命令,這個命令是把 PySpider 全部的組件啓動起來,其中也包括 PhantomJS。
因此咱們代碼怎麼改呢?很簡單。
1
2
3
|
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')
|
只是簡單地加了一個 fetch_type=’js’,點擊綠色的返回箭頭,從新運行一下。
能夠發現,頁面已經被咱們成功加載出來了,簡直不能更帥!
看下面的個性域名,全部咱們須要的 MM 圖片都在那裏面了,因此咱們須要繼續抓取這個頁面。
好,繼續修改 detail_page 方法,而後增長一個 domain_page 方法,用來處理每一個 MM 的個性域名。
1
2
3
4
5
6
7
|
def detail_page(self, response):
domain = 'https:' + response.doc('.mm-p-domain-info li > span').text()
print domain
self.crawl(domain, callback=self.domain_page)
def domain_page(self, response):
pass
|
好,繼續從新 run,預覽一下頁面,終於,咱們看到了 MM 的全部圖片。
嗯,你懂得!
好,照片都有了,那麼咱們就偷偷地下載下來吧~
完善 domain_page 代碼,實現保存簡介和遍歷保存圖片的方法。
在這裏,PySpider 有一個特色,全部的 request 都會保存到一個隊列中,並具備去重和自動重試機制。因此,咱們最好的解決方法是,把每張圖片的請求都寫成一個 request,而後成功後用文件寫入便可,這樣會避免圖片加載不全的問題。
曾經在以前文章寫過圖片下載和文件夾建立的過程,在這裏就很少贅述原理了,直接上寫好的工具類,後面會有完整代碼。
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
|
import os
class Deal:
def __init__(self):
self.path = DIR_PATH
if not self.path.endswith('/'):
self.path = self.path + '/'
if not os.path.exists(self.path):
os.makedirs(self.path)
def mkDir(self, path):
path = path.strip()
dir_path = self.path + path
exists = os.path.exists(dir_path)
if not exists:
os.makedirs(dir_path)
return dir_path
else:
return dir_path
def saveImg(self, content, path):
f = open(path, 'wb')
f.write(content)
f.close()
def saveBrief(self, content, dir_path, name):
file_name = dir_path + "/" + name + ".txt"
f = open(file_name, "w+")
f.write(content.encode('utf-8'))
def getExtension(self, url):
extension = url.split('.')[-1]
return extension
|
這裏麪包含了四個方法。
mkDir:建立文件夾,用來建立 MM 名字對應的文件夾。
saveBrief: 保存簡介,保存 MM 的文字簡介。
saveImg: 傳入圖片二進制流以及保存路徑,存儲圖片。
getExtension: 得到連接的後綴名,經過圖片 URL 得到。
而後在 domain_page 中具體實現以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
def domain_page(self, response):
name = response.doc('.mm-p-model-info-left-top dd > a').text()
dir_path = self.deal.mkDir(name)
brief = response.doc('.mm-aixiu-content').text()
if dir_path:
imgs = response.doc('.mm-aixiu-content img').items()
count = 1
self.deal.saveBrief(brief, dir_path, name)
for img in imgs:
url = img.attr.src
if url:
extension = self.deal.getExtension(url)
file_name = name + str(count) + '.' + extension
count += 1
self.crawl(img.attr.src, callback=self.save_img,
save={'dir_path': dir_path, 'file_name': file_name})
def save_img(self, response):
content = response.content
dir_path = response.save['dir_path']
file_name = response.save['file_name']
file_path = dir_path + '/' + file_name
self.deal.saveImg(content, file_path)
|
以上方法首先獲取了頁面的全部文字,而後調用了 saveBrief 方法存儲簡介。
而後遍歷了 MM 全部的圖片,並經過連接獲取後綴名,和 MM 的姓名以及自增計數組合成一個新的文件名,調用 saveImg 方法保存圖片。
好,基本的東西都寫好了。
接下來。繼續完善一下代碼。初版本完成。
版本一功能:按照淘寶MM姓名分文件夾,存儲MM的 txt 文本簡介以及全部美圖至本地。
可配置項:
- PAGE_START: 列表開始頁碼
- PAGE_END: 列表結束頁碼
- DIR_PATH: 資源保存路徑
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2016-03-25 00:59:45
# Project: taobaomm
from pyspider.libs.base_handler import *
PAGE_START = 1
PAGE_END = 30
DIR_PATH = '/var/py/mm'
class Handler(BaseHandler):
crawl_config = {
}
def __init__(self):
self.base_url = 'https://mm.taobao.com/json/request_top_list.htm?page='
self.page_num = PAGE_START
self.total_num = PAGE_END
self.deal = Deal()
def on_start(self):
while self.page_num <= self.total_num:
url = self.base_url + str(self.page_num)
self.crawl(url, callback=self.index_page)
self.page_num += 1
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')
def detail_page(self, response):
domain = response.doc('.mm-p-domain-info li > span').text()
if domain:
page_url = 'https:' + domain
self.crawl(page_url, callback=self.domain_page)
def domain_page(self, response):
name = response.doc('.mm-p-model-info-left-top dd > a').text()
dir_path = self.deal.mkDir(name)
brief = response.doc('.mm-aixiu-content').text()
if dir_path:
imgs = response.doc('.mm-aixiu-content img').items()
count = 1
self.deal.saveBrief(brief, dir_path, name)
for img in imgs:
url = img.attr.src
if url:
extension = self.deal.getExtension(url)
file_name = name + str(count) + '.' + extension
count += 1
self.crawl(img.attr.src, callback=self.save_img,
save={'dir_path': dir_path, 'file_name': file_name})
def save_img(self, response):
content = response.content
dir_path = response.save['dir_path']
file_name = response.save['file_name']
file_path = dir_path + '/' + file_name
self.deal.saveImg(content, file_path)
import os
class Deal:
def __init__(self):
self.path = DIR_PATH
if not self.path.endswith('/'):
self.path = self.path + '/'
if not os.path.exists(self.path):
os.makedirs(self.path)
def mkDir(self, path):
path = path.strip()
dir_path = self.path + path
exists = os.path.exists(dir_path)
if not exists:
os.makedirs(dir_path)
return dir_path
else:
return dir_path
def saveImg(self, content, path):
f = open(path, 'wb')
f.write(content)
f.close()
def saveBrief(self, content, dir_path, name):
file_name = dir_path + "/" + name + ".txt"
f = open(file_name, "w+")
f.write(content.encode('utf-8'))
def getExtension(self, url):
extension = url.split('.')[-1]
return extension
|
粘貼到你的 PySpider 中運行吧~
其中有一些知識點,我會在後面做詳細的用法總結。你們能夠先體會一下代碼。
保存以後,點擊下方的 run,你會發現,海量的 MM 圖片已經涌入你的電腦啦~
須要解釋?須要我也不解釋!
若是想了解 PySpider 的更多內容,能夠查看官方文檔。