爬虫教程( 2 ) --- 爬虫框架 Scrapy、Scrapy 实战
?
From:https://piaosanlang.gitbooks.io/spiders/content/
?
scrapy-cookbook :https://scrapy-cookbook.readthedocs.io/zh_CN/latest/index.html
?
?
1.?爬蟲框架 Scrapy
?
爬蟲框架中比較好用的是 Scrapy 和 PySpider。
-
PySpider
優點:分布式框架,上手更簡單,操作更加簡便,因為它增加了 WEB 界面,寫爬蟲迅速,集成了phantomjs,可以用來抓取js渲染的頁面。
缺點:自定義程度低
http://docs.pyspider.org/en/latest/Quickstart/
-
Scrapy
優點:自定義程度高,比 PySpider 更底層一些,適合學習研究,需要學習的相關知識多,拿來研究分布式和多線程等等是最合適不過的。
缺點:非分布式框架(可以用 scrapy-redis 分布式框架)
?
Scrapy 官方架構圖
?
Scrapy 主要包括了以下組件:
五個功能模塊
- 引擎(Scrapy):用來處理整個系統的數據流處理, 數據流的指揮官,負責控制數據流(控制各個模塊之間的通信)
- 調度器(Scheduler):?負責引擎發過來的請求URL,壓入隊列成一個URL的優先隊列, 由它來決定下一個要抓取的網址是什么, 同時去除重復的網址
- 下載器(Downloader):?用于下載網頁內容, 并將網頁內容返回給引擎Scrapy
- 爬蟲(Spiders):?爬蟲是主要干活的, 用于從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面
- 項目管道(item Pipeline):?負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被發送到項目管道,并經過幾個特定的次序處理數據。
三大中間件
- 下載器中間件(Downloader Middlewares):?位于Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。
- 爬蟲中間件(Spider Middlewares):?介于 Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。
- 調度中間件(Scheduler Middewares):?介于 Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。
Spider參數
Spider 可以通過接受參數來修改其功能。
spider 參數一般用來定義初始URL或者指定限制爬取網站的部分。 您也可以使用其來配置spider的任何功能。
在運行 crawl 時添加 -a 可以傳遞 Spider 參數:
Spider 在構造器 (constructor) 中獲取參數:
import scrapyclass MySpider(Spider):name = 'myspider'def __init__(self, category=None, *args, **kwargs):super(MySpider, self).__init__(*args, **kwargs)self.start_urls = ['http://www.example.com/categories/%s' % category]腳本運行 Scrapy:https://docs.scrapy.org/en/latest/topics/practices.html
?
?
1.1 Scrapy 介紹
?
Scrapy 是用 Python 開發的一個快速、高層次的 web 抓取框架;
- Scrapy 是一個為了爬取網站數據,提取結構性數據而編寫的應用框架。
- 其最初是為了頁面抓取 (更確切來說, 網絡抓取 )所設計的,也可以應用在獲取 API 所返回的數據 (例如 Amazon Associates Web Services ) 或者 通用的網絡爬蟲。
- Scrapy 用途廣泛,可以用于數據挖掘、監測和自動化測試
- Scrapy 使用了 Twisted 異步網絡庫來處理網絡通訊。
整體架構大致如下
?
Scrapy 運行流程大概如下:
- (1)調度器(Scheduler)從 待下載鏈接 中取出一個鏈接(URL)。
- (2)調度器 啟動采集模塊,即 Spiders模塊。
- (3)采集模塊 把 URL 傳給下載器(Downloader),下載器把資源下載下來。
- (4)提取數據,抽取出目標對象(Item),交給 管道(item pipeline)進行進一步的處理。
- (5)若是解析出的是鏈接(URL),則把 URL 插入到待爬取隊列當中。
?
Scrapy 安裝
文檔 官網文檔(英文):http://doc.scrapy.org/en/latest/intro/install.html
中文文檔(相對官網較老版本):http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html
安裝 Scrapy:pip install scrapy
驗證安裝:輸入 Scrapy 或者 scrapy(大小寫都可以)。如果提示如下命令,就證明安裝成功。
Linux Ubuntu 平臺
安裝 Scrapy
If you prefer to build the python dependencies locally instead of relying on system packages you’ll need to install their required non-python dependencies first:
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
You can install Scrapy with pip after that:sudo pip install Scrapy
驗證安裝:安裝完畢之后,輸入 scrapy。?注意,這里linux下不要輸入Scrapy,linux依然嚴格區分大小寫的
如果出現如下提示,這證明安裝成功
?
?
1.2?Scrapy 入門教程
?
大致流程:
?
1.2.1 創建一個 Scrapy 項目
在開始爬取之前必須創建一個新的 Scrapy 項目。 進入打算存儲代碼的目錄中。運行下列命令: scrapy startproject tutorial
該命令將會創建包含下列內容的 tutorial 目錄,這些文件分別是:
scrapy.cfg: 項目的配置文件;(用于發布到服務器) tutorial/: 該項目文件夾。之后將在此編寫Python代碼。 tutorial/items.py: 項目中的item文件;(定義結構化數據字段field). tutorial/pipelines.py: 項目中的pipelines文件;(用于存放執行后期數據處理的功能,定義如何存儲結構化數據) tutorial/settings.py: 項目的設置文件;(如何修改User-Agent,設置爬取時間間隔,設置代理,配置中間件等等) tutorial/spiders/: 放置spider代碼的目錄;(編寫爬取網站規則)windows 下創建:
Pycharm 打開 Scrapy 工程:
?
1.2.2 定義 Item
Item 定義結構化數據字段,用來保存爬取到的數據;其使用方法和python字典類似。可以通過創建一個 scrapy.Item 類, 并且定義類型為 scrapy.Field 的類屬性來定義一個 Item。首先根據需要從騰訊招聘獲取到的數據對item進行建模。 我們需要從騰訊招聘中獲取 職位名稱、職位詳情頁url、職位類別、人數、工作地點以及發布時間。 對此,在item中定義相應的字段。編輯 tutorial 目錄中的 items.py 文件:
import scrapyclass RecruitItem(scrapy.Item):name = scrapy.Field()detailLink = scrapy.Field()catalog = scrapy.Field()recruitNumber = scrapy.Field()workLocation = scrapy.Field()publishTime = scrapy.Field()?
1.2.3 編寫第一個爬蟲 (Spider)
創建一個 Spider,必須繼承 'scrapy.Spider' 類, 需要定義以下三個屬性:
- name:? spider 名字;必須是唯一的
- start_urls: 初始的 URL 列表
- parse(self, response):每個初始 URL 完成下載后被調用。這個函數要完成的功能:
? ? ? ? 1. 負責解析返回的網頁數據(response.body),提取結構化數據(生成item)
? ? ? ? 2. 生成需要下一頁的請求 URL。
以下為我們的第一個 Spider 代碼,保存在 tutorial/spiders 目錄下的 tencent_spider.py 文件中:
import scrapyclass RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):f = open('tengxun.txt', 'wb')f.write(response.body)f.close()爬取
進入項目的根目錄,執行下列命令啟動 spider:
scrapy crawl tencentcrawl tencent 啟動用于爬取 tencent 的 spider,您將得到類似的輸出:
現在,查看當前目錄,會注意到有文件被創建了: tengxun.txt,正如我們的 parse 方法里做的一樣。
注意,在剛啟動的時候會有一段 error 信息,不用理會(以后會說明,可現在自行查找結果)
?
剛才發生了什么?
- Scrapy 為 Spider 的 start_urls 屬性中的每個 URL 創建了 scrapy.Request 對象,并將 parse 方法作為回調函數(callback)賦值給了 Request。
- Request 對象經過調度,執行生成 scrapy.http.Response 對象并送回給 parse() 方法。
?
提取 Item
Scrapy?內置的 Selectors 模塊提供了對? XPath 和 CSS Selector 的支持。也可以單獨拿出來使用
單獨使用 示例:
from scrapy import Selectortemp_string = ''' <bookstore><book><title lang="eng">Harry Potter</title><price>29.99</price></book><book><title lang="eng">Learning XML</title><price>39.95</price></book> </bookstore> '''if __name__ == '__main__':s = Selector(text=temp_string)print(s.xpath('//book[1]/title/text()').extract_first())print(s.xpath('//book[1]/price/text()').extract_first())passXPath 表達式的例子及對應的含義:
/html/head/title 選擇<HTML>文檔中 <head> 標簽內的 <title> 元素 /html/head/title/text() 選擇上面提到的 <title> 元素的文字 //td 選擇所有的 <td> 元素 //div[@class="mine"] 選擇所有具有 class="mine" 屬性的 div 元素Selector 有四個基本的方法:
xpath() 傳入xpath表達式,返回該表達式所對應的所有節點的selector list列表 。 css() 傳入CSS表達式,返回該表達式所對應的所有節點的selector list列表. extract() 序列化該節點為unicode字符串并返回list。 re() 根據傳入的正則表達式對數據進行提取,返回unicode字符串list列表。嘗試 Selector 選擇器
為了介紹 Selector的使用方法,接下來我們將要使用內置的 scrapy shell 。Scrapy Shell 需要您預裝好 IPython (一個擴展的Python終端)。您需要進入項目的根目錄,執行下列命令來啟動 shell:?
scrapy shell "http://hr.tencent.com/position.php?&start=0#a"注解: 當您在終端運行 Scrapy 時,請一定記得給 url 地址加上引號,否則包含參數的 url (例如 & 字符)會導致 Scrapy 運行失敗。
shell 的輸出類似:
當 shell 載入后,將得到一個包含 response 數據的本地 response 變量。
輸入 response.body 將輸出 response 的包體, 輸出 response.headers 可以看到 response 的包頭。?
當輸入 response.selector 時, 將獲取到一個response 初始化的類 Selector 的對象。
此時,可以通過使用 response.selector.xpath() 或 response.selector.css() 來對 response 進行查詢。
scrapy 對 response.selector.xpath() 及 response.selector.css() 提供了一些快捷方式,例如 response.xpath() 或 response.css()
response.xpath('//title') [<Selector xpath='//title' data=u'<title>\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058</title'>]response.xpath('//title').extract() [u'<title>\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058</title>']print response.xpath('//title').extract()[0] <title>職位搜索 | 社會招聘 | Tencent 騰訊招聘</title>response.xpath('//title/text()') <Selector xpath='//title/text()' data=u'\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058'>response.xpath('//title/text()')[0].extract() u'\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058'print response.xpath('//title/text()')[0].extract() 職位搜索 | 社會招聘 | Tencent 騰訊招聘response.xpath('//title/text()').re('(\w+):') [u'\u804c\u4f4d\u641c\u7d22',u'\u793e\u4f1a\u62db\u8058',u'Tencent',u'\u817e\u8baf\u62db\u8058']?
提取數據
現在,從頁面中提取些有用的數據。
# 通過 XPath 選擇該頁面中網站列表里所有 lass=even 元素 site = response.xpath('//*[@class="even"]')# 職位名稱: print site[0].xpath('./td[1]/a/text()').extract()[0] # TEG15-運營開發工程師(深圳)# 職位名稱詳情頁: print site[0].xpath('./td[1]/a/@href').extract()[0] position_detail.php?id=20744&keywords=&tid=0&lid=0# 職位類別: print site[0].xpath('./td[2]/text()').extract()[0] # 技術類對于 .xpath() 調用返回 selector 組成的 list, 因此可以拼接更多的 .xpath() 來進一步獲取某個節點。
for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]print name, detailLink, catalog,recruitNumber,workLocation,publishTime在我們的 tencent_spider.py 文件修改成如下代碼:
import scrapyclass RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]print name, detailLink, catalog,recruitNumber,workLocation,publishTime如圖所示:
現在嘗試再次爬取 hr.tencent.com,您將看到爬取到的網站信息被成功輸出:
scrapy crawl tencent運行過程:
?
使用 item
Item 對象是自定義的 python 字典??梢允褂脴藴实淖值湔Z法來獲取到其每個字段的值。輸入 `scrapy shell'
import scrapyclass RecruitItem(scrapy.Item):name = scrapy.Field()detailLink = scrapy.Field()catalog = scrapy.Field()recruitNumber = scrapy.Field()workLocation = scrapy.Field()publishTime = scrapy.Field()item = RecruitItem() item['name'] = 'sanlang' item['name'] 'sanlang'一般來說,Spider 將會將爬取到的數據以 Item 對象返回。所以為了將爬取的數據返回,最終 tencent_spider.py 代碼將是:
import scrapy from tutorial.items import RecruitItem class RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield item現在對 hr.tencent.com 進行爬取將會產生 RecruitItem 對象:
?
保存爬取到的數據
最簡單存儲爬取的數據的方式是使用 Feed exports:
scrapy crawl tencent -o items.json該命令將采用 JSON 格式對爬取的數據進行序列化,生成 items.json 文件。
如果需要對爬取到的item做更多更為復雜的操作,您可以編寫 Item Pipeline 。 類似于我們在創建項目時對Item做的,用于您編寫自己的 tutorial/pipelines.py 也被創建。 不過如果您僅僅想要保存item,您不需要實現任何的pipeline。
?
1.2.4?Item Pipelines
當 Item 在 Spider 中被收集之后,它將會被傳遞到 Item Pipeline。
每個 Item Pipeline 組件接收到 Item,定義一些操作行為,比如決定此 Item 是丟棄而存儲。
item pipeline 的一些典型應用:
- 驗證爬取的數據 (檢查 item 包含某些字段,比如說 name 字段)。
- 查重 (并丟棄)。
- 將爬取結果保存到文件或者數據庫中。
?
編寫 item pipeline
編寫 item pipeline 很簡單,item pipiline 組件是一個獨立的 Python 類,必須實現 process_item 方法:
- process_item(self, item, spider):當 Item 在 Spider 中被收集之后,都需要調用該方法。參數: ?item - 爬取的結構化數據。 spider – 爬取該 item 的 spider
- open_spider(self, spider):當 spider 被開啟時,這個方法被調用。參數:spider ? – 被開啟的spider
- close_spider(spider):當 spider 被關閉時,這個方法被調用。參數:spider – 被關閉的spider
?
將 item 寫入 JSON 文件
以下 pipeline 將所有爬取到的 item,存儲到一個獨立地 items.json 文件,每行包含一個序列化為 'JSON' 格式的 'item':
import jsonclass JsonWriterPipeline(object):def __init__(self):self.file = open('items.json', 'wb')def process_item(self, item, spider):line = json.dumps(dict(item),ensure_ascii=False) + "\n"self.file.write(line)return item?
啟用一個Item Pipeline組件
為了啟用 Item Pipeline 組件,必須將它的類添加到 settings.py 文件 ITEM_PIPELINES 配置,就像下面這個例子:
ITEM_PIPELINES = {#'tutorial.pipelines.PricePipeline': 300,'tutorial.pipelines.JsonWriterPipeline': 800, }分配給每個類的整型值,確定了他們運行的順序,item按數字從低到高的順序,通過 pipeline,通常將這些數字定義在0-1000范圍內。
?
在這里優化:
以下 pipeline 將所有爬取到的 item,存儲到一個獨立地 items.json 文件,每行包含一個序列化為 'JSON' 格式的 'item':
import json import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()針對 spider 里面的 utf-8 編碼格式去掉 .encode('utf-8')
item = RecruitItem() item['name']=name.encode('utf-8') item['detailLink']=detailLink.encode('utf-8') item['catalog']=catalog.encode('utf-8') item['recruitNumber']=recruitNumber.encode('utf-8') item['workLocation']=workLocation.encode('utf-8') item['publishTime']=publishTime.encode('utf-8')?
將 item 寫入 MongoDB
- from_crawler(cls, crawler):如果使用,這類方法被調用創建爬蟲管道實例。必須返回管道的一個新實例。crawler提供存取所有Scrapy核心組件配置和信號管理器; 對于pipelines這是一種訪問配置和信號管理器 的方式。參數: crawler (Crawler object) – crawler that uses this pipeline
例子中,我們將使用 pymongo 將 Item 寫到 MongoDB。MongoDB 的地址和數據庫名稱在 Scrapy setttings.py 配置文件中;這個例子主要是說明如何使用 from_crawler() 方法
import pymongoclass MongoPipeline(object):collection_name = 'scrapy_items'def __init__(self, mongo_uri, mongo_db):self.mongo_uri = mongo_uriself.mongo_db = mongo_db@classmethoddef from_crawler(cls, crawler):return cls(mongo_uri=crawler.settings.get('MONGO_URI'),mongo_db=crawler.settings.get('MONGO_DATABASE', 'items'))def open_spider(self, spider):self.client = pymongo.MongoClient(self.mongo_uri)self.db = self.client[self.mongo_db]def close_spider(self, spider):self.client.close()def process_item(self, item, spider):self.db[self.collection_name].insert(dict(item))return item?
?
1.3 Spider 類
?
Spider類 定義了如何爬取某個(或某些)網站。包括了爬取的動作(例如:是否跟進鏈接) 以及如何從網頁的內容中提取結構化數據(爬取item)。 換句話說,Spider 就是定義爬取的動作及分析某個網頁(或者是有些網頁)的地方。
?
class scrapy.spider.Spider
scrapy 為我們提供了5種 spide r用于構造請求,解析數據、返回 item。常用的就 scrapy.spider、scrapy.crawlspider兩種。
Spider 是最簡單的 spider。每個 spider 必須繼承自該類。Spider 并沒有提供什么特殊的功能。其僅僅請求給定的 start_urls / start_requests,并根據返回的結果調用 spider 的 parse 方法。
- name:定義 spider 名字的字符串。例如,如果spider爬取 mywebsite.com ,該 spider 通常會被命名為 mywebsite
- allowed_domains:可選。包含了spider允許爬取的域名(domain)列表(list)
- start_urls:初始 URL 列表。當沒有制定特定的 URL 時,spider 將從該列表中開始進行爬取。
- start_requests():當 spider 啟動爬取并且未指定 start_urls 時,該方法被調用。如果您想要修改最初爬取某個網站。
- parse(self, response):當請求 url 返回網頁沒有指定回調函數時,默認下載回調方法。參數:response (Response) – 返回網頁信息的 response
- log(message[, level, component]):使用 scrapy.log.msg() 方法記錄(log)message。 更多數據請參見?Logging
下面是 spider 常用到的 屬性 和 方法(scrapy一覽及源碼解析:https://www.cnblogs.com/pontoon/p/10247589.html)
| 屬性、方法 | 功能 | 簡述 |
| name | 爬蟲的名稱 | 啟動爬蟲的時候會用到 |
| start_urls | 起始 url | 是一個列表,默認被 start_requests 調用 |
| allowd_doamins | 對 url 進行的簡單過濾 | 當請求 url 沒有被 allowd_doamins 匹配到時,會報一個非常惡心的錯, |
| start_requests() | 第一次請求 | 自己的 spider 可以重寫,突破一些簡易的反爬機制 |
| custom_settings | 定制 settings | 可以對每個爬蟲定制 settings 配置 |
| from_crawler | 實例化入口 | 在 scrapy 的各個組件的源碼中,首先執行的就是它 |
關于 spider 我們可以定制 start_requests、可以單獨的設置 custom_settings、也可以設置請
例如,如果您需要在啟動時以 POST 登錄某個網站,你可以這么寫:
class MySpider(scrapy.Spider):name = 'myspider'def start_requests(self):return [scrapy.FormRequest("http://www.example.com/login",formdata={'user': 'john', 'pass': 'secret'},callback=self.logged_in)]def logged_in(self, response):# here you would extract links to follow and return Requests for# each of them, with another callbackpass?
Spider 示例
讓我們來看一個例子:
import scrapyclass MySpider(scrapy.Spider):name = 'example.com'allowed_domains = ['example.com']start_urls = ['http://www.example.com/1.html','http://www.example.com/2.html','http://www.example.com/3.html',]def parse(self, response):self.log('A response from %s just arrived!' % response.url)另一個在單個回調函數中返回多個 Request 以及 Item 的例子:
import scrapy from myproject.items import MyItemclass MySpider(scrapy.Spider):name = 'example.com'allowed_domains = ['example.com']start_urls = ['http://www.example.com/1.html','http://www.example.com/2.html','http://www.example.com/3.html',]def parse(self, response):sel = scrapy.Selector(response)for h3 in response.xpath('//h3').extract():yield MyItem(title=h3)for url in response.xpath('//a/@href').extract():yield scrapy.Request(url, callback=self.parse)?
案例:騰訊招聘網翻頁功能
import scrapy from tutorial.items import RecruitItem import re class RecruitSpider(scrapy.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')if catalog:item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield itemnextFlag = response.xpath('//*[@id="next"]/@href')[0].extract()if 'start' in nextFlag:curpage = re.search('(\d+)',response.url).group(1)page =int(curpage)+10url = re.sub('\d+',str(page),response.url)print urlyield scrapy.Request(url, callback=self.parse)執行:scrapy crawl tencent -L INFO
?
?
1.4 CrawlSpider 類
?
scrapy.spiders.CrawlSpider
CrawlSpider?定義了一些規則(rule)來提供跟進 link 的方便的機制。除了從 Spider 繼承過來的(您必須提供的)屬性外(name、allow_domains),其提供了一個新的屬性:
- rules:包含一個(或多個) 規則對象的集合(list)。 每個Rule對爬取網站的動作定義了特定操作。 如果多個 rule 匹配了相同的鏈接,則根據規則在本集合中被定義的順序,第一個會被使用。
- parse_start_url(response):當 start_url 的請求返回時,該方法被調用
?
爬取規則(Crawling rules)
class scrapy.contrib.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)
- link_extractor:其定義了如何從爬取到的頁面中提取鏈接。
- callback:指定 spider 中哪個函數將會被調用。 從 link_extractor 中每獲取到鏈接時將會調用該函數。該回調函數接受一個response 作為其第一個參數。注意:當編寫爬蟲規則時,請避免使用 parse作為回調函數。由于 CrawlSpider 使用 parse 方法來實現其邏輯,如果您覆蓋了 parse方法,crawl spider將會運行失敗。
- cb_kwargs:包含傳遞給回調函數的參數 (keyword argument) 的字典。
- follow:是一個布爾(boolean)值,指定了根據該規則從response提取的鏈接是否需要跟進。 如果callback為None,follow默認設置為True ,否則默認為False。
- process_links:指定該spider中哪個的函數將會被調用,從link_extractor中獲取到鏈接列表時將會調用該函數。該方法常用于過濾參數
- process_request:指定該spider中哪個的函數將會被調用,該規則提取到每個request時都會調用該函數 (用來過濾request)
?
CrawlSpider 案例
還是以騰訊招聘為例,給出配合 rule 使用 CrawlSpider 的例子:
首先運行
scrapy shell "http://hr.tencent.com/position.php?&start=0#a"導入匹配規則:
from scrapy.linkextractors import LinkExtractor page_lx = LinkExtractor(allow=('position.php?&start=\d+'))查詢匹配結果:
page_lx.extract_links(response)沒有查到:
page_lx = LinkExtractor(allow=(r'position\.php\?&start=\d+')) page_lx.extract_links(response)[Link(url='http://hr.tencent.com/position.php?start=10', text='2', fragment='', nofollow=False), Link(url='http://hr.tencent.com/position.php?start=20', text='3', fragment='', nofollow=False), Link(url='http://hr.tencent.com/position.php?start=30', text='4', fragment='', nofollow=False), Link(url='http://hr.tencent.com/position.php?start=40', text='5', fragment='', nofollow=False), Link(url='http://hr.tencent.com/position.php?start=50', text='6', fragment='', nofollow=False), Link(url='http://hr.tencent.com/position.php?start=60', text='7', fragment='', nofollow=False), Link(url='http://hr.tencent.com/position.php?start=70', text='...', fragment='', nofollow=False), Link(url='http://hr.tencent.com/position.php?start=1300', text='131', fragment='', nofollow=False)] len(page_lx.extract_links(response))那么,scrapy shell 測試完成之后,修改以下代碼
#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的鏈接 page_lx = LinkExtractor(allow=('start=\d+'))rules = [ #提取匹配,并使用spider的parse方法進行分析;并跟進鏈接(沒有callback意味著follow默認為True) Rule(page_lx, callback='parse',follow=True) ]這么寫對嗎??callback 千萬不能寫 parse,一定運行有錯誤!!保存以下代碼為 tencent_crawl.py
# -*- coding:utf-8 -*- import scrapy from tutorial.items import RecruitItem from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractorclass RecruitSpider(CrawlSpider):name = "tencent_crawl"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的鏈接page_lx = LinkExtractor(allow=('start=\d+'))rules = [#提取匹配,并使用spider的parse方法進行分析;并跟進鏈接(沒有callback意味著follow默認為True)Rule(page_lx, callback='parseContent',follow=True)]def parseContent(self, response):print response.urlfor sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')if catalog:item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield item可以修改配置文件settings.py,添加 LOG_LEVEL='INFO'
運行: scrapy crawl tencent_crawl
?
process_links 參數:動態網頁爬取,動態 url 的處理
在爬取?https://bitsharestalk.org?的時候,發現網站會為每一個 url 增加一個 sessionid 屬性,可能是為了標記用戶訪問歷史,而且這個 seesionid 隨著每次訪問都會動態變化,這就為爬蟲的去重處理(即標記已經爬取過的網站)和提取規則增加了難度。
比如:https://bitsharestalk.org/index.php?board=5.0?會變成?https://bitsharestalk.org/index.phpPHPSESSID=9771d42640ab3c89eb77e8bd9e220b53&board=5.0
下面介紹幾種處理方法
僅適用你的爬蟲使用的是 scrapy.contrib.spiders.CrawlSpider,在這個內置爬蟲中,你提取 url 要通過 Rule類來進行提取,其自帶了對提取后的 url 進行加工的函數。
rules = (Rule(LinkExtractor(allow=(r"https://bitsharestalk\.org/index\.php\?PHPSESSID\S*board=\d+\.\d+$",r"https://bitsharestalk\.org/index\.php\?board=\d+\.\d+$")),process_links='link_filtering' # 默認函數process_links), Rule(LinkExtractor(allow=(r" https://bitsharestalk\.org/index\.php\?PHPSESSID\S*topic=\d+\.\d+$",r"https://bitsharestalk\.org/index\.php\?topic=\d+\.\d+$",), ),callback="extractPost",follow=True, process_links='link_filtering'),Rule(LinkExtractor(allow=(r"https://bitsharestalk\.org/index\.php\?PHPSESSID\S*action=profile;u=\d+$",r"https://bitsharestalk\.org/index\.php\?action=profile;u=\d+$",), ),callback="extractUser", process_links='link_filtering'))def link_filtering(self, links):ret = []for link in links:url = link.url# print "This is the yuanlai ", link.urlurlfirst, urllast = url.split(" ? ")if urllast:link.url = urlfirst + " ? " + urllast.split(" & ", 1)[1]# print link.urlreturn linkslink_filtering() 函數對 url 進行了處理,過濾掉了 sessid,關于 Rule類的 process_links 函數和 links 類,官方文檔中并沒有給出介紹,給出一個參考?https://groups.google.com/forum/#!topic/scrapy-users/RHGtm_2GO1M(也許需要梯子,你懂得)
如果你是自己實現的爬蟲,那么 url 的處理更是可定制的,只需要自己處理一下就可以了。
?
process_request 參數:修改請求參數
import re from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractorclass WeiboSpider(CrawlSpider):name = 'weibo'allowed_domains = ['weibo.com']# 不加www,則匹配不到 cookie, get_login_cookie()方法正則代完善start_urls = ['http://www.weibo.com/u/1876296184']rules = (Rule(# 微博個人頁面的規則,或/u/或/n/后面跟一串數字LinkExtractor(allow=r'^http:\/\/(www\.)?weibo.com/[a-z]/.*'),process_request='process_request',callback='parse_item', follow=True),)cookies = Nonedef process_request(self, request):link = request.urlpage = re.search(r'page=\d*', link).group()tp = re.search(r'type=\d+', link).group()new_request = request.replace(cookies=self.cookies, url='.../questionType?' + page + "&" + tp)return new_request?
?
1.5 Logging
?
Scrapy 提供了 log 功能。您可以通過 logging 模塊使用。
?
Log levels
Scrapy 提供5層 logging 級別:
默認情況下 python 的 logging 模塊將日志打印到了標準輸出中,且只顯示了大于等于 WARNING 級別的日志,這說明默認的日志級別設置為 WARNING(日志級別等級CRITICAL > ERROR > WARNING > INFO > DEBUG,默認的日志格式為DEBUG級別
?
如何設置 log 級別
您可以通過終端選項(command line option) --loglevel/-L 或 LOG_LEVEL 來設置log級別。
-
scrapy crawl tencent_crawl -L INFO
-
可以修改配置文件 settings.py,添加??LOG_LEVEL='INFO'
?
在 Spider 中添加 log
Scrapy 為每個 Spider 實例記錄器提供了一個 logger,可以這樣訪問:
import scrapyclass MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://scrapinghub.com']def parse(self, response):self.logger.info('Parse function called on %s', response.url)logger 是用 Spider 的名稱創建的,但是你可以用你想要的任何自定義 logging。例如:
import logging import scrapylogger = logging.getLogger('zhangsan')class MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://scrapinghub.com']def parse(self, response):logger.info('Parse function called on %s', response.url)Logging 設置
以下設置可以被用來配置logging: LOG_ENABLED 默認: True,啟用logging LOG_ENCODING 默認: 'utf-8',logging使用的編碼 LOG_FILE 默認: None,logging輸出的文件名 LOG_LEVEL 默認: 'DEBUG',log的最低級別 LOG_STDOUT 默認: False。如果為 True,進程所有的標準輸出(及錯誤)將會被重定向到log中。例如,執行 print 'hello' ,其將會在Scrapy log中顯示。?
案例 (一) (?self.logger )
tencent_crawl.py 添加日志信息如下:
'''添加日志信息'''print 'print',response.urlself.logger.info('info on %s', response.url)self.logger.warning('WARNING on %s', response.url)self.logger.debug('info on %s', response.url)self.logger.error('info on %s', response.url)完整版如下:
# -*- coding:utf-8 -*- import scrapy from tutorial.items import RecruitItem from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractorclass RecruitSpider(CrawlSpider):name = "tencent_crawl"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的鏈接page_lx = LinkExtractor(allow=('start=\d+'))rules = [#提取匹配,并使用spider的parse方法進行分析;并跟進鏈接(沒有callback意味著follow默認為True)Rule(page_lx, callback='parseContent',follow=True)]def parseContent(self, response):#print("print settings: %s" % self.settings['LOG_FILE'])'''添加日志信息'''print 'print',response.urlself.logger.info('info on %s', response.url)self.logger.warning('WARNING on %s', response.url)self.logger.debug('info on %s', response.url)self.logger.error('info on %s', response.url)for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=nameitem['detailLink']=detailLinkif catalog:item['catalog']=catalogitem['recruitNumber']=recruitNumberitem['workLocation']=workLocationitem['publishTime']=publishTimeyield item在 settings 文件中,修改添加信息
LOG_FILE='ten.log' LOG_LEVEL='INFO'接下來執行:scrapy crawl tencent_crawl?;蛘?command line 命令行執行:
scrapy crawl tencent_crawl --logfile 'ten.log' -L INFO輸出如下
print http://hr.tencent.com/position.php?start=10 print http://hr.tencent.com/position.php?start=1340 print http://hr.tencent.com/position.php?start=0 print http://hr.tencent.com/position.php?start=1320 print http://hr.tencent.com/position.php?start=1310 print http://hr.tencent.com/position.php?start=1300 print http://hr.tencent.com/position.php?start=1290 print http://hr.tencent.com/position.php?start=1260ten.log 文件中記錄,可以看到級別大于 INFO 日志輸出
2016-08-15 23:10:57 [tencent_crawl] INFO: info on http://hr.tencent.com/position.php?start=70 2016-08-15 23:10:57 [tencent_crawl] WARNING: WARNING on http://hr.tencent.com/position.php?start=70 2016-08-15 23:10:57 [tencent_crawl] ERROR: info on http://hr.tencent.com/position.php?start=70 2016-08-15 23:10:57 [tencent_crawl] INFO: info on http://hr.tencent.com/position.php?start=1320 2016-08-15 23:10:57 [tencent_crawl] WARNING: WARNING on http://hr.tencent.com/position.php?start=1320 2016-08-15 23:10:57 [tencent_crawl] ERROR: info on http://hr.tencent.com/position.php?start=1320?
案例(二)(?logging.getLogger )
tencent_spider.py 添加日志信息如下:logger = logging.getLogger('zhangsan')
'''添加日志信息'''print 'print',response.urlself.logger.info('info on %s', response.url)self.logger.warning('WARNING on %s', response.url)self.logger.debug('info on %s', response.url)self.logger.error('info on %s', response.url)完整版如下:
import scrapy from tutorial.items import RecruitItem import re import logginglogger = logging.getLogger('zhangsan')class RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):#logger.info('spider tencent Parse function called on %s', response.url)'''添加日志信息'''print 'print',response.urllogger.info('info on %s', response.url)logger.warning('WARNING on %s', response.url)logger.debug('info on %s', response.url)logger.error('info on %s', response.url)for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=nameitem['detailLink']=detailLinkif catalog:item['catalog']=catalogitem['recruitNumber']=recruitNumberitem['workLocation']=workLocationitem['publishTime']=publishTimeyield itemnextFlag = response.xpath('//*[@id="next"]/@href')[0].extract()if 'start' in nextFlag:curpage = re.search('(\d+)',response.url).group(1)page =int(curpage)+10url = re.sub('\d+',str(page),response.url)print urlyield scrapy.Request(url, callback=self.parse)在 settings 文件中,修改添加信息
LOG_FILE='tencent.log' LOG_LEVEL='WARNING'接下來執行:scrapy crawl tencent 。或者command line命令行執行:
scrapy crawl tencent --logfile 'tencent.log' -L WARNING輸出信息
print http://hr.tencent.com/position.php?&start=0 http://hr.tencent.com/position.php?&start=10 print http://hr.tencent.com/position.php?&start=10 http://hr.tencent.com/position.php?&start=20 print http://hr.tencent.com/position.php?&start=20 http://hr.tencent.com/position.php?&start=30tencent.log 文件中記錄,可以看到級別大于 INFO 日志輸出
2016-08-15 23:22:59 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=0 2016-08-15 23:22:59 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=0 2016-08-15 23:22:59 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=10 2016-08-15 23:22:59 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=10?
小試 LOG_STDOUT
settings.py
LOG_FILE='tencent.log' LOG_STDOUT=True LOG_LEVEL='INFO'執行:scrapy crawl tencent
輸出:空
tencent.log 文件中記錄,可以看到級別大于 INFO 日志輸出
2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=110 2016-08-15 23:28:32 [stdout] INFO: print 2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=110 2016-08-15 23:28:32 [zhangsan] INFO: info on http://hr.tencent.com/position.php?&start=110 2016-08-15 23:28:32 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=110 2016-08-15 23:28:32 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=110 2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=120 2016-08-15 23:28:33 [stdout] INFO: print 2016-08-15 23:28:33 [stdout] INFO: http://hr.tencent.com/position.php?&start=120 2016-08-15 23:28:33 [zhangsan] INFO: info on http://hr.tencent.com/position.php?&start=120 2016-08-15 23:28:33 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=120 2016-08-15 23:28:33 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=120?
scrapy 之 Logging 使用
#coding:utf-8 ###################### ##Logging的使用 ###################### import logging ''' 1. logging.CRITICAL - for critical errors (highest severity) 致命錯誤 2. logging.ERROR - for regular errors 一般錯誤 3. logging.WARNING - for warning messages 警告+錯誤 4. logging.INFO - for informational messages 消息+警告+錯誤 5. logging.DEBUG - for debugging messages (lowest severity) 低級別 ''' logging.warning("This is a warning")logging.log(logging.WARNING,"This is a warning")#獲取實例對象 logger=logging.getLogger() logger.warning("這是警告消息") #指定消息發出者 logger = logging.getLogger('SimilarFace') logger.warning("This is a warning")#在爬蟲中使用log import scrapy class MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://scrapinghub.com']def parse(self, response):#方法1 自帶的loggerself.logger.info('Parse function called on %s', response.url)#方法2 自己定義個loggerlogger.info('Parse function called on %s', response.url)''' Logging 設置 ? LOG_FILE ? LOG_ENABLED ? LOG_ENCODING ? LOG_LEVEL ? LOG_FORMAT ? LOG_DATEFORMAT ? LOG_STDOUT命令行中使用 --logfile FILE Overrides LOG_FILE--loglevel/-L LEVEL Overrides LOG_LEVEL--nolog Sets LOG_ENABLED to False '''import logging from scrapy.utils.log import configure_loggingconfigure_logging(install_root_handler=False) #定義了logging的些屬性 logging.basicConfig(filename='log.txt',format='%(levelname)s: %(levelname)s: %(message)s',level=logging.INFO ) #運行時追加模式 logging.info('進入Log文件') logger = logging.getLogger('SimilarFace') logger.warning("也要進入Log文件")?
?
1.6 Settings
?
Scrapy 設置(settings)提供了定制 Scrapy 組件的方法??梢钥刂瓢ê诵?core),插件(extension),pipeline 及 spider 組件。比如 設置 Json Pipeliine、LOG_LEVEL
內置設置列表請參考內置設置參考手冊
?
獲取設置值 (Populating the settings)
設置可以通過多種方式設置,每個方式具有不同的優先級。
下面以?優先級降序?的方式給出方式列表:
- 命令行選項(Command line Options)(最高優先級) 。命令行傳入的參數具有最高的優先級。?使用選項 -s (或 --set) 來覆蓋一個 (或更多) 選項。比如:scrapy crawl myspider -s LOG_FILE=scrapy.log
- 每個 spider 的設置 (?scrapy.spiders.Spider.custom_settings )。 class MySpider(scrapy.Spider):name = 'myspider'custom_settings = {'SOME_SETTING': 'some value',}
- 項目設置模塊 (Project settings module)。項目設置模塊是 Scrapy 項目的標準配置文件。即 setting.py myproject.settings
?
如何訪問配置 (settings)
In a spider, the settings are available through self.settings:
class MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://example.com']def parse(self, response):print("Existing settings: %s" % self.settings.attributes.keys())Settings can be accessed through the scrapy.crawler.Crawler.settings attribute of the Crawler that is passed to from_crawler method in extensions, middlewares and item pipelines:
class MyExtension(object):def __init__(self, log_is_enabled=False):if log_is_enabled:print("log is enabled!")@classmethoddef from_crawler(cls, crawler):settings = crawler.settingsreturn cls(settings.getbool('LOG_ENABLED'))?
案例 (?self.settings 使用?)
添加一行代碼 print("Existing settings: %s" % self.settings['LOG_FILE'])
# -*- coding:utf-8 -*- import scrapy from tutorial.items import RecruitItem from scrapy.contrib.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor import loggingclass RecruitSpider(CrawlSpider):name = "tencent_crawl"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的鏈接page_lx = LinkExtractor(allow=('start=\d+'))rules = [#提取匹配,并使用spider的parse方法進行分析;并跟進鏈接(沒有callback意味著follow默認為True)Rule(page_lx, callback='parseContent',follow=True)]def parseContent(self, response):print response.urlprint("Existing settings: %s" % self.settings['LOG_FILE'])self.logger.info('Parse function called on %s', response.url)for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')if catalog:item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield item內置設置參考手冊
BOT_NAME:默認: 'scrapybot'。當您使用 startproject 命令創建項目時其也被自動賦值。 CONCURRENT_ITEMS:默認: 100。Item Processor(即 Item Pipeline) 同時處理(每個response的)item的最大值。 CONCURRENT_REQUESTS:默認: 16。Scrapy downloader 并發請求(concurrent requests)的最大值。 DEFAULT_REQUEST_HEADERS 默認: {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en', } Scrapy HTTP Request使用的默認header。 DEPTH_LIMIT:默認: 0。爬取網站最大允許的深度(depth)值。如果為0,則沒有限制。 DOWNLOAD_DELAY:默認: 0。下載器在下載同一個網站下一個頁面前需要等待的時間。該選項可以用來限制爬取速度, 減輕服務器壓力。同時也支持小數: DOWNLOAD_DELAY = 0.25 # 250 ms of delay: 該設置影響(默認啟用的) RANDOMIZE_DOWNLOAD_DELAY 設置。 默認情況下,Scrapy在兩個請求間不等待一個固定的值, 而是使用0.5到1.5之間的一個隨機值 * DOWNLOAD_DELAY 的結果作為等待間隔。 DOWNLOAD_TIMEOUT:默認: 180。下載器超時時間(單位: 秒)。 ITEM_PIPELINES:默認: {}。保存項目中啟用的pipeline及其順序的字典。該字典默認為空,值(value)任意。 不過值(value)習慣設置在0-1000范圍內。 樣例: ITEM_PIPELINES = { 'mybot.pipelines.validate.ValidateMyItem': 300, 'mybot.pipelines.validate.StoreMyItem': 800, } LOG_ENABLED:默認: True。是否啟用logging。 LOG_ENCODING:默認: 'utf-8'。logging使用的編碼。 LOG_LEVEL:默認: 'DEBUG'。log的最低級別。可選的級別有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。 USER_AGENT:默認: "Scrapy/VERSION (+http://scrapy.org)"。爬取的默認User-Agent,除非被覆蓋。?
?
1.7 陽光熱線問政平臺( 東莞 )
?
目標網址:http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4
items.py:添加以下代碼
from scrapy.item import Item, Fieldclass SunItem(Item):number = Field()url = Field()title = Field()content = Field()在 spiders 目錄下新建一個自定義 SunSpider.py
from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor # from tutorial.items import SunItem import scrapy import urllib import time import reclass SunSpider(CrawlSpider):name = 'sun0769'num = 0allow_domain = ['http://wz.sun0769.com/']start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4']rules = {Rule(LinkExtractor(allow='page'), process_links='process_request', follow=True),Rule(LinkExtractor(allow=r'/html/question/\d+/\d+\.shtml$'), callback='parse_content')}def process_request(self, links):ret = []for link in links:try:page = re.search(r'page=\d*', link.url).group()tp = re.search(r'type=\d+', link.url).group()link.url = 'http://wz.sun0769.com/index.php/question/questionType?' + page + "&" + tpexcept BaseException as e:print(e)ret.append(link)return retdef parse_content(self, response):item = SunItem()url = response.urltitle = response.xpath('//*[@class="greyframe"]/div/div/strong/text()')[0].extract().strip()number = response.xpath('//*[@class="greyframe"]/div/div/strong/text()')[0].extract().strip().split(':')[-1]content = response.xpath('//div[@class="c1 text14_2"]/text()').extract()[0].strip()item['url'] = urlitem['title'] = titleitem['number'] = numberitem['content'] = contentprint(dict(item))# yield itemif __name__ == '__main__':from scrapy import cmdlinecmdline.execute('scrapy crawl sun0769'.split())pass在 pipelines.py:添加如下代碼
import json import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()settings.py 添加如下代碼(啟用組件)
ITEM_PIPELINES = {'tutorial.pipelines.JsonWriterPipeline': 300, }window 下調試
在項目根目錄下新建 main.py 文件,用于調試
from scrapy import cmdline cmdline.execute('scrapy crawl sun0769'.split())?
?
?
2. scrapy 案例 和 scrapyd 部署
?
案例 1:騰訊招聘
騰訊招聘:https://careers.tencent.com/search.html
items.py:添加以下代碼
from scrapy.item import Item, Fieldclass TencentItem(Item):title = Field()catalog = Field()workLocation = Field()recruitNumber = Field()duty = Field()Job_requirement= Field()url = Field()publishTime = Field()在 spiders 目錄下新建一個自定義 tencent_info.py
# -*- coding:utf-8 -*- from scrapy.selector import Selector from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule import re from tutorial.items import TencentItemclass TencentSpider(CrawlSpider):name = "tengxun_info"allowed_domains = ["tencent.com"]start_urls = ["http://hr.tencent.com/position.php"]rules = [Rule(LinkExtractor(allow=("start=\d+"))),Rule(LinkExtractor(allow=("position_detail\.php")), follow=True, callback='parse_item')]def parse_item(self,response):item =TencentItem()title = response.xpath('//*[@id="sharetitle"]/text()')[0].extract()workLocation = response.xpath('//*[@class="lightblue l2"]/../text()')[0].extract()catalog = response.xpath('//*[@class="lightblue"]/../text()')[0].extract()recruitNumber = response.xpath('//*[@class="lightblue"]/../text()').re('(\d+)')[0]duty_pre = response.xpath('//*[@class="squareli"]')[0].extract()duty = re.sub('<.*?>','',duty_pre)Job_requirement_pre = response.xpath('//*[@class="squareli"]')[1].extract()Job_requirement = re.sub('<.*?>','',Job_requirement_pre)item['title']=titleitem['url']=response.urlitem['workLocation']=workLocationitem['catalog']=catalogitem['recruitNumber']=recruitNumberitem['duty']=dutyitem['Job_requirement']=Job_requirementyield item在 pipelines.py:添加如下代碼
import json import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()settings.py 添加如下代碼(啟用組件)
ITEM_PIPELINES = {'tutorial.pipelines.JsonWriterPipeline': 300, }在項目根目錄下新建 main.py 文件,用于調試
from scrapy import cmdline cmdline.execute('scrapy crawl tengxun_info'.split())?
案例 2:國家食品藥品監督管理總局
目標網站( 藥品 ---> 國產藥品?):https://www.nmpa.gov.cn/
抓包結果如圖:
items.py:添加以下代碼
from scrapy import Field import scrapyclass Sfda1Item(scrapy.Item):# define the fields for your item here like:data = scrapy.Field()在spiders目錄下新建一個自定義spider
# -*- coding: utf-8 -*- import scrapy from scrapy.http import FormRequest # from tutorial.items import Sfda1Item import urllib import reclass sfdaSpider(scrapy.Spider):name = 'sfda'allowed_domains = ['sfda.gov.cn']def start_requests(self):url = 'http://app1.sfda.gov.cn/datasearch/face3/search.jsp'data = {'tableId': '32','State': '1','bcId': '124356639813072873644420336632','State': '1','tableName': 'TABLE32','State': '1','viewtitleName': 'COLUMN302','State': '1','viewsubTitleName': 'COLUMN299,COLUMN303','State': '1','curstart': '1','State': '1','tableView': urllib.quote("國產藥品商品名"),'State': '1',}yield FormRequest(url=url, formdata=data, meta={'data': data}, callback=self.parseContent)def parseContent(self, response):for site in response.xpath('//a').re(r'callbackC,\'(.*?)\',null'):id = re.search('.+Id=(.*?)$', site).group(1)# print idurl = 'http://app1.sfda.gov.cn/datasearch/face3/content.jsp?tableId=32&tableName=TABLE32' \'&tableView=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&Id=' + idyield scrapy.Request(url,headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'},callback=self.ParseDetail)data = response.meta['data']data['curstart'] = str(int(data['curstart']) + 1)yield FormRequest(url=response.request.url, formdata=data, meta={'data': data}, callback=self.parseContent)def ParseDetail(self, response):item = dict()for site in response.xpath('//table[1]/.//tr')[1:-1]:try:if not site.xpath('./td/text()').extract()[0]:continuename = site.xpath('./td/text()').extract()[0]value = re.sub('<.*?>', '', site.xpath('./td')[1].extract()).strip()print(name, value)item[name] = valueexcept BaseException as e:print('error', e)# sfa = Sfda1Item()item_data = dict()item_data['data'] = itemyield item_dataif __name__ == '__main__':from scrapy import cmdlinecmdline.execute('scrapy crawl sfda'.split())pass在 pipelines.py:添加如下代碼
import json import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()settings.py:添加如下代碼(啟用組件)
ITEM_PIPELINES = {'tutorial.pipelines.JsonWriterPipeline': 300, }在項目根目錄下新建main.py文件,用于調試
from scrapy import cmdline cmdline.execute('scrapy crawl sfda -L INFO'.split())?
3. 使用 scrapyd 管理爬蟲
scrapyd 是由 scrapy 官方提供的爬蟲管理工具,使用它我們可以非常方便地上傳、控制爬蟲并且查看運行日志。
參考官方文檔 :?http://scrapyd.readthedocs.org/en/latest/api.html
使用 scrapyd 和我們直接運行?scrapy crawl myspider?有什么區別呢?scrapyd 同樣是通過上面的命令運行爬蟲的,不同的是它提供一個JSON web service 監聽的請求,我們可以從任何一臺可以連接到服務器的電腦發送請求安排爬蟲運行,或者停止正在運行的爬蟲。甚至,我們可以使用它提供的API上傳新爬蟲而不必登錄到服務器上進行操作。
?
安裝 scrapyd:pip install scrapyd
參考文檔:https://github.com/scrapy/scrapyd-client
運行 scrapyd 服務。直接運行命令 scrapyd 即可:scrapyd
默認情況下scrapyd 監聽 0.0.0.0:6800 端口,運行 scrapyd 后在瀏覽器 http://localhost:6800/ 即可查看到當前可以運行的項目:
web 接口:http://localhost:6800/
部署 scrapy 項目:直接使用 scrapyd-client 提供的 scrapyd-deploy 工具:pip install scrapyd-client
直接在項目根目錄:修改工程目錄下的 scrapy.cfg 文件
[deploy:scrapyd2] #默認情況下并沒有scrapyd2,它只是一個名字,可以在配置文件中寫多個名字不同的deploy url = http://scrapyd.mydomain.com/api/scrapyd/ #要部署項目的服務器的地址 username = john #訪問服務器所需的用戶名和密碼(如果不需要密碼可以不寫) password = secret其中的 username 和 password 用于在部署時驗證服務器的 HTTP basic authentication,須要注意的是這里的用戶密碼并不表示訪問該項目須要驗證,而是登錄服務器用的。Ubuntu/Windows:
[deploy:tutorial_deploy] url = http://192.168.17.129:6800/ project = tutorial username = enlong password = test部署項目到服務器:直接在項目根目錄:
Windows:python c:\Python27\Scripts\scrapyd-deploy
Ubuntu:scrapyd-deploy tutorial_deploy -p tutorial
部署操作會打包你的當前項目,如果當前項目下有setup.py文件,就會使用它,沒有的會就會自動創建一個。(如果后期項目需要打包的話,可以根據自己的需要修改里面的信息,也可以暫時不管它)。從返回的結果里面,可以看到部署的狀態,項目名稱,版本號和爬蟲個數,以及當前的主機名稱。
?
查看項目 spider
通過 scrapyd-deploy -l 查看當前目錄下的可以使用的部署方式(target)
Windows/Ubuntu
或再次打開 http://localhost:6800/,?也可以看到 Available projects: default, tutorial
列出服務器上所有的項目,檢查tutorial_deploy 是否已經部署上去了:
scrapyd-deploy -L tutorial_deploy default tutorial?
API
scrapyd 的 web 界面比較簡單,主要用于監控,所有的調度工作全部依靠接口實現.
參考官方文檔:http://scrapyd.readthedocs.org/en/stable/api.html
?
開啟爬蟲 schedule
curl http://localhost:6800/schedule.json -d project=tutorial -d spider=tencentWindows/Ubuntu 注意:執行時 cd 到項目根目錄執行curl http://localhost:6800/schedule.json -d project=tutorial -d spider=tencent {"status": "ok", "jobid": "94bd8ce041fd11e6af1a000c2969bafd", "node_name": "ubuntu"}?
停止 cancel
curl http://localhost:6800/cancel.json -d project=tutorial -d job=94bd8ce041fd11e6af1a000c2969bafd?
列出爬蟲
curl http://localhost:6800/listspiders.json?project=tutorial?
刪除項目
curl http://localhost:6800/delproject.json -d project=tutorial?
更新
對于 scrapyd 默認項目 (即是啟動 scrapyd 命令后看到的default項目):
只有在 scrapy 項目里啟動 scrapyd 命令時才有默認項目,默認項目就是當前的 scrapy 項目
如果在非 scrapy 項目下執行 scrapyd, 是看不到 default 的
注意:執行時 cd 到項目根目錄執行
?
第一種 情況
cfg:[deploy] url = http://192.168.17.129:6800/ project = tutorial username = enlong password = test運行結果:python@ubuntu:~/project/tutorial$ scrapyd-deploy Packing version 1471069533 Deploying to project "tutorial" in http://192.168.17.129:6800/addversion.json Server response (200): {"status": "ok", "project": "tutorial", "version": "1471069533", "spiders": 1, "node_name": "ubuntu"}?
第二種情況
cfg:[deploy:tutorial_deploy] url = http://192.168.17.129:6800/ project = tutorial username = enlong password = test運行結果:python@ubuntu:~/project/tutorial$ scrapyd-deploy tutorial_deploy Packing version 1471069591 Deploying to project "tutorial" in http://192.168.17.129:6800/addversion.json Server response (200): {"status": "ok", "project": "tutorial", "version": "1471069591", "spiders": 1, "node_name": "ubuntu"}?
?
4. 為 scrapyd 創建服務
?
Systemd 是 Linux 系統工具,用來啟動守護進程,已成為大多數發行版的標準配置。首先檢查你的系統中是否安裝有 systemd 并確定當前安裝的版本
Systemd 入門教程:命令篇:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
Systemd 入門教程:實戰篇:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html
?
systemd --version
sudo vi /lib/systemd/system/scrapyd.serviceThen add the following line into that file:
[Unit] Description=scrapyd After=network.target Documentation=http://scrapyd.readthedocs.org/en/latest/api.html[Service] User=root ExecStart=/usr/local/bin/scrapyd --logfile /var/scrapyd/scrapyd.log[Install] WantedBy=multi-user.target[Unit]區塊通常是配置文件的第一個區塊,用來定義 Unit 的元數據,以及配置與其他 Unit 的關系
After:如果該字段指定的 Unit After 也要啟動,那么必須在當前 service 之前啟動 Documentation:服務文檔地址 Description:簡短描述[Service]區塊用來 Service 的配置,只有 Service 類型的 Unit 才有這個區塊
ExecStart:啟動當前服務的命令[Install]通常是配置文件的最后一個區塊,用來定義如何啟動,以及是否開機啟動
WantedBy:它的值是一個或多個 Target,當前 Unit 激活時(enable)符號鏈接會放入/etc/systemd/system目錄下面以 Target 名 + .wants后綴構成的子目錄中At this point, we can now start our new service by running the command below:
sudo systemctl start scrapyd sudo service scrapyd startTo check the status of the service, issue the following command:
sudo systemctl status scrapyd?
開機啟動
To make the services start at boot time, use the command below:
sudo systemctl enable scrapyd Created symlink from /etc/systemd/system/multi-user.target.wants/scrapyd.service to /lib/systemd/system/scrapyd.service.取消開機啟動
sudo systemctl disable scrapyd?
?
5. scrapyd 服務器添加認證信息
?
我們也可以在scrapyd前面加一層反向代理來實現用戶認證。以nginx為例, 配置nginx
安裝 nginx:sudo apt-get install nginx
配置 nginx:vi /etc/nginx/nginx.conf 修改如下:
# Scrapyd local proxy for basic authentication. # Don't forget iptables rule. # iptables -A INPUT -p tcp --destination-port 6800 -s ! 127.0.0.1 -j DROPhttp {server {listen 6801;location / {proxy_pass http://127.0.0.1:6800/;auth_basic "Restricted";auth_basic_user_file /etc/nginx/conf.d/.htpasswd;}} }/etc/nginx/htpasswd/user.htpasswd?里設置的用戶名 enlong和密碼都是test 修改配置文件,添加用戶信息
Nginx 使用 htpasswd 創建用戶認證
?
apache htpasswd 命令用法實例
1、如何利用 htpasswd 命令添加用戶?
htpasswd -bc .passwd www.leapsoul.cn php
在bin目錄下生成一個.passwd文件,用戶名www.leapsoul.cn,密碼:php,默認采用MD5加密方式
2、如何在原有密碼文件中增加下一個用戶?
htpasswd -b .passwd leapsoul phpdev
去掉c選項,即可在第一個用戶之后添加第二個用戶,依此類推
重啟 nginx:sudo service nginx restart
測試 Nginx
F:\_____gitProject_______\curl-7.33.0-win64-ssl-sspi\tieba_baidu>curl http://localhost:6800/schedule.json -d project=tutorial -d spider=tencent -u enlong:test {"status": "ok", "jobid": "5ee61b08428611e6af1a000c2969bafd", "node_name": "ubuntu"}配置 scrapy.cfg 文件
[deploy] url = http://192.168.17.129:6801/ project = tutorial username = admin password = admin注意上面的url已經修改為了nginx監聽的端口。
提醒: 記得修改服務器上scrapyd的配置bind_address字段為127.0.0.1,以免可以從外面繞過nginx, 直接訪問6800端口。 關于配置可以參看本文后面的配置文件設置.
修改配置文件:sudo vi /etc/scrapyd/scrapyd.conf
[scrapyd] bind_address = 127.0.0.1scrapyd?啟動的時候會自動搜索配置文件,配置文件的加載順序為
/etc/scrapyd/scrapyd.conf?/etc/scrapyd/conf.d/*?scrapyd.conf?~/.scrapyd.conf
最后加載的會覆蓋前面的設置
默認配置文件如下, 可以根據需要修改
[scrapyd] eggs_dir = eggs logs_dir = logs items_dir = items jobs_to_keep = 5 dbs_dir = dbs max_proc = 0 max_proc_per_cpu = 4 finished_to_keep = 100 poll_interval = 5 bind_address = 0.0.0.0 http_port = 6800 debug = off runner = scrapyd.runner application = scrapyd.app.application launcher = scrapyd.launcher.Launcher[services] schedule.json = scrapyd.webservice.Schedule cancel.json = scrapyd.webservice.Cancel addversion.json = scrapyd.webservice.AddVersion listprojects.json = scrapyd.webservice.ListProjects listversions.json = scrapyd.webservice.ListVersions listspiders.json = scrapyd.webservice.ListSpiders delproject.json = scrapyd.webservice.DeleteProject delversion.json = scrapyd.webservice.DeleteVersion listjobs.json = scrapyd.webservice.ListJobs關于配置的各個參數具體含義,可以參考官方文檔:http://scrapyd.readthedocs.io/en/stable/config.html
?
采集感興趣的網站:(1)京東 (2)豆瓣 (3)論壇 。。。。
?
?
?
總結
以上是生活随笔為你收集整理的爬虫教程( 2 ) --- 爬虫框架 Scrapy、Scrapy 实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OFD文件结构--带签名
- 下一篇: Lambda 表达式详解~深入JVM实现