第一次完整使用scrapy框架,通過一個簡單的實例來記錄一下使用框架的基本步驟,希望自己越來越熟悉這個強大框架的使用。
安裝Scrapy
我這邊是搭配anaconda使用的,因此直接在創建的環境(名爲py36
)中使用conda install scrapy
即可在此工作環境下面安裝好scrapy
此外,也可以直接
pip install Scrapy
入門案例
首先明確總體的步驟:
- 創建一個Scrapy項目
- 定義提取的結構化數據(Item)
- 編寫爬取網站的 Spider 並提取出結構化數據(Item)
- 編寫 Item Pipelines 來存儲提取到的Item(即結構化數據)
一. 新建一個項目(scrapy startproject)
使用anaconda的話首先需要進入已經安裝scrapy的環境(activate env1
),然後進入自定義的項目目錄中(比如說新建一個Scrapy_project文件夾存放所有的爬蟲項目,再新建一個Taobao文件夾存放豆瓣網頁的爬蟲項目,那麼就要進入\Scrapy_project\Taobao),運行下列命令:
scrapy startproject TaobaoSpider
可以在當前根目錄下輸入tree /F
查看文件目錄樹,可以看到在Taobao目錄下生成了TaobaoSpider這個項目的文件夾,文件夾名同項目名。
這些文件分別是:
- scrapy.cfg: 項目的配置文件。
- TaobaoSpider/: 項目的Python模塊,將會從這裏引用代碼。
- TaobaoSpider/items.py: 項目的目標文件。
- TaobaoSpider/pipelines.py: 項目的管道文件。
- TaobaoSpider/settings.py: 項目的設置文件。
- TaobaoSpider/spiders/: 存儲爬蟲代碼目錄。
二. 明確目標(TaobaoSpider/items.py)
我們打算抓取淘寶網上某商品關鍵字的商品信息:價格、商品名稱、銷量
Item.py
文件定義結構化數據字段,用來保存爬取到的數據,有點像 Python 中的 dict,但是提供了一些額外的保護減少錯誤。所以說需要修改該文件將想要爬取到的字段填入該文件進行預定義即可。
打開items.py
,可以發現基本結構已經生成完畢,包括導入庫,類名都已經定義好,只需要按照註釋所說定義好想要爬取的域字段即可,修改如下:
import scrapy
class TaobaospiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# 序號
num = scrapy.Field()
# 商品名稱
name = scrapy.Field()
# 價格
price = scrapy.Field()
# 銷量
sales = scrapy.Field()
pass
三. 製作爬蟲 (spiders/TaobaoMovie.py)
之前說過 TaobaoSpider/spiders/是存儲爬蟲代碼目錄的文件夾,可以發現現在這個文件夾是空的,因爲我們還沒有開始製作一個爬蟲。
1. 創建爬蟲
在spiders的同一目錄下輸入
scrapy genspider TaobaoProduct www.taobao.com
TaobaoProduct 是爬蟲名字,後面是爬取的目標域名
scrapy genspider [options] <name> <domain>
輸入完畢之後,可以發現在spiders文件夾下面會自動創建一個TaobaoProduct.py
文件,這個就是爬蟲文件,我們需要在這文件裏編輯、爬取目標數據。可以打開該文件,默認增加了以下代碼:
import scrapy
class TaobaoproductSpider(scrapy.Spider):
name = "TaobaoProduct" # 爬蟲名字
allowed_domains = ["www.taobao.com"]
start_urls = ['www.taobao.com']
def parse(self, response):
pass
-
name = “” :這個爬蟲的識別名稱,必須是唯一的,在不同的爬蟲必須定義不同的名字。
-
allow_domains = [] 是搜索的域名範圍,也就是爬蟲的約束區域,規定爬蟲只爬取這個域名下的網頁,不存在的URL會被忽略。
-
start_urls = () :爬取的URL元祖/列表。爬蟲從這裏開始抓取數據,所以,第一次下載的數據將會從這些urls開始。其他子URL將會從這些起始URL中繼承性生成。
-
parse(self, response) :解析的方法,每個初始URL完成下載後將被調用,調用的時候傳入從每一個URL傳回的Response對象來作爲唯一參數,主要作用如下:負責解析返回的網頁數據(response.body),提取結構化數據(生成item)、生成需要下一頁的URL請求。
2. 取數據
接下來我們要做的就是填充TaobaoProduct.py
文件,使用相應的庫(re、BeautifulSoup)來爬取所需數據。
- 首先,我們需要明白這個類之間的內在關係:parse子方法的response是對start_urls中的url的網頁響應。傳入參數response就是待解析的網頁代碼。
- 其次,當對url響應不僅僅只是簡單的get時,比如網頁需要模擬登錄,那麼就響應就需要post數據,或者加入get參數時,此時就要用到start_requests() 方法。
該方法必須返回一個可迭代對象(iterable)。該對象包含了spider用於爬取的第一個Request。當spider啓動爬取並且未指定URL時,該方法被調用。當指定了URL時,make_requests_from_url() 將被調用來創建Request對象。該方法僅僅會被Scrapy調用一次,默認實現是使用 start_urls 的url生成Request。
恰好,淘寶網頁就需要我們先模擬登錄,再去爬取數據,此時如果不使用start_requests() 方法,會發現爬蟲結果報錯403。
- 模擬登錄:我們先解決模擬登錄的問題,這邊我們因爲發現淘寶網頁的post data字段太多,並且似乎經過了加密處理,post用戶名密碼登錄方法實現登錄似乎比較麻煩,所以直接使用了基於cookie的模擬登錄:將登錄後的cookie複製下來,作爲參數獲取網頁響應,這裏用到了start_requests() 方法:
cookies = {}
c = '_uab_collina=151221395076409730939696; cna=476mEjlDmngCAdrFmUz2zpFC; hng=CN%7Czh-CN%7CCNY%7C156; t=bf63eaafda26ff86c76af4014fe3b509; tg=0; x=e%3D1%26p%3D*%26s%3D0%26c%3D0%26f%3D0%26g%3D0%26t%3D0%26__ll%3D-1%26_ato%3D0; miid=7820446351806933749; lid=%E6%96%AF%E7%A7%91%E8%8F%B2%E4%BA%8C%E5%BE%B7; enc=4fo9WJlEp1W00%2F6vzoD7251gSfQcg%2Bev4Q74Kqct7rl8%2FXiHKNAM%2F6txKDZyg9c8K0thDU5IoRUkwTcv1d5Qdw%3D%3D; _umdata=6AF5B463492A874D4FF2535D5E094AE8FEBC511C9E2C38CECE0987D5E2210676A8E3549BA5C0F630CD43AD3E795C914CED09BBFF8F1CE9836FAF732B92BDE3D8; UM_distinctid=1664e12832255-02254c9411ae0f-5701732-144000-1664e128323195; thw=cn; lc=VypURZ%2BxkK2WDFmBpHzI; log=lty=Ug%3D%3D; uc3=vt3=F8dByEnZoL5dWiOVyHM%3D&id2=UUGk3bJF6X9rfg%3D%3D&nk2=qBFl9Ii6zaXunA%3D%3D&lg2=VT5L2FSpMGV7TQ%3D%3D; tracknick=%5Cu65AF%5Cu79D1%5Cu83F2%5Cu4E8C%5Cu5FB7; lgc=%5Cu65AF%5Cu79D1%5Cu83F2%5Cu4E8C%5Cu5FB7; _cc_=UtASsssmfA%3D%3D; mt=ci=29_1; v=0; uc1=cookie14=UoTZ4M4rAgkdvA%3D%3D; cookie2=1268809b458a9c1730ca8ca319cfdb0b; _tb_token_=e9787eb14b139; cookieCheck=80558; isg=BDEx7CEZyWJGF2P2D6JnwH6eQL3BmwjQYjYEzRNGLfgXOlGMW261YN9YWI756T3I; l=bBxpeERlvJpR20w2BOCanurza77OSIRYYuPzaNbMi_5Qq6T_k2QOliyMBF96Vj5RsxYB4-L8Y1J9-etkZ'
for line in c.split(';'):
name, value = line.strip().split('=', 1)
cookies[name] = value
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, cookies=cookies)
使用這個方法之後,那麼parse方法傳入的參數response第一個時刻就是帶上了cookie的網頁響應,實現了登錄。
- 填充parse方法中代碼取數據:導入類初始化item對象、解析代碼填入item屬性字段、自動翻頁
# -*- coding: utf-8 -*-
import scrapy
from ..items import TaobaospiderItem
import re
cookies = {}
c = '_uab_collina=151221395076409730939696; cna=476mEjlDmngCAdrFmUz2zpFC; hng=CN%7Czh-CN%7CCNY%7C156; t=bf63eaafda26ff86c76af4014fe3b509; tg=0; x=e%3D1%26p%3D*%26s%3D0%26c%3D0%26f%3D0%26g%3D0%26t%3D0%26__ll%3D-1%26_ato%3D0; miid=7820446351806933749; lid=%E6%96%AF%E7%A7%91%E8%8F%B2%E4%BA%8C%E5%BE%B7; enc=4fo9WJlEp1W00%2F6vzoD7251gSfQcg%2Bev4Q74Kqct7rl8%2FXiHKNAM%2F6txKDZyg9c8K0thDU5IoRUkwTcv1d5Qdw%3D%3D; _umdata=6AF5B463492A874D4FF2535D5E094AE8FEBC511C9E2C38CECE0987D5E2210676A8E3549BA5C0F630CD43AD3E795C914CED09BBFF8F1CE9836FAF732B92BDE3D8; UM_distinctid=1664e12832255-02254c9411ae0f-5701732-144000-1664e128323195; thw=cn; lc=VypURZ%2BxkK2WDFmBpHzI; log=lty=Ug%3D%3D; uc3=vt3=F8dByEnZoL5dWiOVyHM%3D&id2=UUGk3bJF6X9rfg%3D%3D&nk2=qBFl9Ii6zaXunA%3D%3D&lg2=VT5L2FSpMGV7TQ%3D%3D; tracknick=%5Cu65AF%5Cu79D1%5Cu83F2%5Cu4E8C%5Cu5FB7; lgc=%5Cu65AF%5Cu79D1%5Cu83F2%5Cu4E8C%5Cu5FB7; _cc_=UtASsssmfA%3D%3D; mt=ci=29_1; v=0; uc1=cookie14=UoTZ4M4rAgkdvA%3D%3D; cookie2=1268809b458a9c1730ca8ca319cfdb0b; _tb_token_=e9787eb14b139; cookieCheck=80558; isg=BDEx7CEZyWJGF2P2D6JnwH6eQL3BmwjQYjYEzRNGLfgXOlGMW261YN9YWI756T3I; l=bBxpeERlvJpR20w2BOCanurza77OSIRYYuPzaNbMi_5Qq6T_k2QOliyMBF96Vj5RsxYB4-L8Y1J9-etkZ'
for line in c.split(';'):
name, value = line.strip().split('=', 1)
cookies[name] = value
class TaobaoproductSpider(scrapy.Spider):
num = 0 # 序號
count = 0 # 迭代次數
name = "TaobaoProduct"
# allowed_domains = ["www.taobao.com"]
start_urls = ['https://s.taobao.com/search?q=%E4%B9%A6']
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, cookies=cookies)
def parse(self, response):
item = TaobaospiderItem()
names = re.findall(r'\"raw_title\":\"(.*?)\"', response.body.decode())
prices = re.findall(r'\"view_price\":\"(.*?)\"', response.body.decode())
sales = re.findall(r'\"view_sales\":\"(.*?)\"', response.body.decode())
for i in range(len(names)):
item['name'] = names[i]
item['price'] = prices[i]
item['sales'] = sales[i]
item['num'] = self.num
self.num = self.num + 1
yield item
if self.count < 10000:
self.count = self.count + 1
next_url = self.start_urls[0] + '&s=' + str(self.count * 44)
yield scrapy.Request(next_url, cookies=cookies)
pass
這裏最需要注意的是導入items.py
中定義的類,from ..items import TaobaospiderItem
導入之後,初始化一個item對象,然後給對象的屬性賦值
四. 配置項(settings.py/pipeline.py)
settings.py
修改重要的配置項,可以減少爬蟲被目標網頁發現的機率:
ROBOTSTXT_OBEY = True # 是否遵守robots.txt
CONCURRENT_REQUESTS = 16 # 開啓線程數量,默認16
AUTOTHROTTLE_START_DELAY = 3 # 開始下載時限速並延遲時間 默認3
AUTOTHROTTLE_MAX_DELAY = 60 # 高併發請求時最大延遲時間 默認60
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
# 以上幾個參數對本地緩存進行配置,如果開啓本地緩存會優先讀取本地緩存,從而加快爬取速度
USER_AGENT = 'projectname (+http://www.yourdomain.com)'
# 對requests的請求頭進行配置,比如可以修改爲‘Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36’同樣可以避免服務器返回403
pipelines.py
當Item在Spider中被收集之後,它將會被傳遞到Item Pipeline,一些組件會按照一定的順序執行對Item的處理。如果僅僅想要保存item,則不需要實現的pipeline。item pipeline的一些典型應用有:
- 清理html數據
- 驗證爬取的數據
- 去重並丟棄
- 將爬取的結果保存到數據庫中或文件中
我這裏只是定義了寫文件的操作(這個操作其實可以通過運行爬蟲使用命令加參數實現)
import json
import codecs
# import pandas as pd
class TaobaospiderPipeline(object):
def __init__(self):
self.file = codecs.open('taobao.json', 'wb', encoding='utf-8')
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file.write(line)
return item
五. 運行爬蟲
語法:scrapy crawl <spider>
因爲這裏在pipelines.py
文件中寫了寫文件的操作,因此不需要加入額外參數運行爬蟲即可實現保存,同樣地,可以選擇scrapy crawl <spider> -o <filename>
將結果保存到特定文件中。
taobao.json文件內容如下:
至此,梳理了一遍scrapy框架使用的大體步驟,實例操作時候最大的感受就是需要設置好settings.py
參數,把併發數調低點,不然IP就被封掉了,要好幾天纔會恢復。
其中,pipelines/middlewares/settings.py
的使用還需要進一步通過實驗去熟悉,更復雜的項目可能就需要對這些文件進行復雜的操作了。
最後,這個實例裏面基本沒有用到xpath,但是xpath是非常好用簡潔的,以後應該會記錄基本使用方法。