Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 其能夠應用在數據挖掘,信息處理或存儲歷史數據等一系列的程序中。
其最初是爲了頁面抓取 (更確切來講, 網絡抓取 )所設計的, 也能夠應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。html
Scrapy 使用了 Twisted異步網絡庫來處理網絡通信。總體架構大體以下:python
Scrapy主要包括瞭如下組件:web
Scrapy運行流程大概以下:算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Linux:
pip3 install scrapy
window:
注:本人Python版本是
64
位,如果
32
位,請到官網下載對應版本文件!
1
、pip3 install wheel 有請跳過;
2
、安裝關聯模塊pypiwin32:pip3 install pypiwin32 有請跳過;
3
、在http:
/
/
www.lfd.uci.edu
/
~gohlke
/
pythonlibs
/
#twisted 下載 而後pip3 install 文件路徑 安裝 twisted
4
、pip3 install scrapy
5
、python
import
scrapy 報錯:
from
..
import
etree ImportError: DLL load failed: 找不到指定的程序。
解決辦法:因爲本地缺乏lxml文件或是lxml文件不符
pip3 uninstall lxml 先卸載已經安裝的lxml
官網下載新的lxml:http:
/
/
www.lfd.uci.edu
/
~gohlke
/
pythonlibs
/
#lxml 下載 而後pip3 install 文件路徑 安裝 lxml
如果有其餘報錯,查看少哪一個模塊,pip安裝便可,例如:缺乏cryptography模塊;pip3 install cryptography
|
文件下載,請猛擊: Twisted 64位下載 lxml 64位下載數據庫
1、基本命令json
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
|
#在當前目錄下操做
#建立項目:
scrapy startproject 項目名
例:scrapy startproject sp1
sp1項目結構介紹
-
sp1
-
spiders 目錄
-
middlewares.py 中間件
-
items.py 格式化
-
pipelines.py 持久化
-
settings.cfg 配置
-
scrapy.cfg 配置
#建立爬蟲:
cd sp1
scrapy genspider 爬蟲名 域名
例如:scrapy genspider example example.com
scrapy genspider baidu baidu.com
scrapy genspider xiaohuar xiaohuar.com
scrapy genspider jiandan jandan.net
/
ooxx
PS:
查看全部命令:scrapy gensipider
-
l
查看模板命令:scrapy gensipider
-
d 模板名稱
#注意:通常建立爬蟲文件時,以網站域名命名
#展現爬蟲應用列表
-
scrapy
list
#單獨執行某爬蟲,進入project
-
scrapy crawl 爬蟲名
例如:scrapy crawl baidu
# 有執行日誌流程
-
scrapy crawl baidu
-
-
nolog
# 無執行日誌
#可能會遇到執行報錯 forbidden 的問題,這是由於在爬取數據的時候,須要遵循爬取協議,若是對方服務器不容許爬取那就會返回這個錯誤。
#真的須要暴力爬取的話,那就在settings配置文件中,把協議改爲False。這樣就能獲取到數據!
#打印爬取內容:
在你建立的爬蟲項目下,有一個以項目名命名的py文件【個人是baidu.py】,這裏邊有一個爬蟲的類,類下有個parse方法,用於接收返回值response
在函數下添加以下代碼:
print
(response.text)。表明執行完畢以後打印爬取的內容!
response.text
# 字符串類型數據
response.body
# 字節類型數據
|
文件說明:
scrapy.cfg 項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中)
items.py 設置數據存儲模板,用於結構化數據,如:Django的Model
pipelines 數據處理行爲,如:通常結構化的數據持久化
settings.py 配置文件,如:遞歸的層數、併發數,延遲下載等
spiders 爬蟲目錄,如:建立文件,編寫爬蟲規則
2、小試牛刀 緩存
既然咱們已經熟悉了基本操做,那咱們就來扒拉一下校花網。服務器
1
2
3
4
5
|
建立scrapy項目
-
scrapy startproject sp1
建立爬蟲 (一個項目下能夠建立多個爬蟲)
-
scrapy genspider xiaohuar xiaohuar.com
建立完成以後,能夠用pycharm打開建立的項目,欣賞一下本身的傑做!
|
在 spiders 文件下,會有一個建立的爬蟲文件 - xiaohuar.py。文件內部代碼以下:cookie
# -*- coding: utf-8 -*- import scrapy class XiaohuarSpider(scrapy.Spider): name = 'xiaohuar' #爬蟲名稱 allowed_domains = ['xiaohuar.com'] #容許的域名 start_urls = ['http://www.xiaohuar.com/hua/'] #起始URL def parse(self, response): # 訪問起始URL並獲取結果後的回調函數 pass
咱們發現,回調函數parse中沒有任何代碼,咱們能夠自定義操做,如:咱們寫個 print(response.text) 意思是打印返回的結果。[自行添加測試哈]網絡
關於編碼的問題:
咱們爬取下來的信息,可能由於字符編碼的問題,致使打印的結果是亂碼,解決方法是把以下代碼粘貼到爬蟲文件的頭部。
1
2
|
import
sys,os
sys.stdout
=
io.TextIOWrapper(sys.stdout.
buffer
,encoding
=
'gb18030'
)
|
執行此爬蟲文件,則在終端進入項目目錄執行以下命令:
1
|
scrapy crawl xiaohuar
-
-
nolog
|
3、scrapy框架內部方法介紹
一、選擇器
當爬蟲爬取下來全部頁面,可是咱們僅須要某一段標籤,或是超連接地址,再或者是須要某張照片or文本,這怎麼辦呢?scrapy框架內部爲咱們封裝了Selector方法,方便咱們去截取。
引用方法:
1
|
from
scrapy.selector
import
Selector
|
應用:
#處理返回的字符串,轉換成標籤 hxs = Selector(response=response) # hxs.xpath(條件) #經過條件過濾查找,相似與CSS中的選擇器 #lists = hxs.xpath(條件).extract() #獲取條件查找到的全部標籤,返回結果是一個列表,列表內都是找到的一個個對應的標籤 #lists = hxs.xpath(條件).extract_first() #獲取條件查找到的第一個標籤 #關於條件: 經常使用操做: // 表明子子孫孫中查找 / 兒子中查找 標籤名[@屬性名="屬性值"] 定位直接找到某一類標籤 支持正則匹配查找,語法結構以下: 標籤名[re:test(@屬性名,"正則匹配信息語法")] /@屬性名 獲取某個標籤內的屬性 /text() 獲取標籤的文本 contains 包含 starts-with 起始 也支持鏈式操做,及查找的時候能夠找當前標籤下的子標籤或是子孫標籤 標籤名[@屬性名="屬性值"]/a/@href 子標籤a標籤的href屬性 標籤名[@屬性名="屬性值"]//a/@href 子孫標籤a標籤的href屬性 特殊的,也支持在當前標籤再作一次過濾,語法不變但查找是須要指定當前位置也就是點(.); hxs = Selector(response=response) user_list = hxs.xpath('//div[@class="item masonry_brick"]') for item in user_list: price = item.xpath('.//span[@class="price"]/text()').extract_first() url = item.xpath('//div[@class="item_t"]/div[@class="img"]//a/@href').extract_first() print(price, url) #item.xpath(.//) 相對於當前 子孫 中找 #item.xpath(./) 相對於當前 子代 中找 #item.xpath(*/) 相對於當前 子代 中找 #item.xpath('a') 相對於當前 找子代的a標籤
# hxs = Selector(response=response).xpath('//a') # print(hxs) # hxs = Selector(response=response).xpath('//a[2]') # print(hxs) # hxs = Selector(response=response).xpath('//a[@id]') # print(hxs) # hxs = Selector(response=response).xpath('//a[@id="i1"]') # print(hxs) # hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]') # print(hxs) # hxs = Selector(response=response).xpath('//a[contains(@href, "link")]') # print(hxs) # hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]') # print(hxs) # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]') # print(hxs) # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/text()').extract() # print(hxs) # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/@href').extract() # print(hxs) # hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract() # print(hxs) # hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first() # print(hxs) # ul_list = Selector(response=response).xpath('//body/ul/li') # for item in ul_list: # v = item.xpath('./a/span') # # 或 # # v = item.xpath('a/span') # # 或 # # v = item.xpath('*/a/span') # print(v)
scrapy框架內部,也封裝了Request方法,與Python內置的requests的應用一致。
引入方法:
1
|
from
scrapy.http
import
Request
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
分析HTTP請求
-
請求方式
-
URL
-
Http經常使用請求頭
各個參數對應的不一樣名稱:
user
-
agent: 當前用戶訪問使用的設備信息
content
-
type
:xxxx 請求體數據類型
host:請求訪問的主地址
Referer:訪問請求來自哪一個頁面
cookies:關鍵信息
PS:POST請求,不一樣的請求體數據類型對應着不一樣的請求頭
數據是:Form Data,請求頭類型爲:Content
-
Type
:application
/
x
-
www
-
form
-
urlencoded;
數據是:request payload 請求頭類型爲:content
-
type
: application
/
json
|
class Request(object_ref): def __init__(self, url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None, flags=None): ......... #url = 請求路由地址 #method 請求方法,默認GET #headers 請求頭 #body 傳值 #cookies cookies #dont_filter 是否過濾,默認False url請求過濾
import scrapy from scrapy.selector import HtmlXPathSelector from scrapy.http.request import Request class DigSpider(scrapy.Spider): # 爬蟲應用的名稱,經過此名稱啓動爬蟲命令 name = "dig" # 容許的域名 allowed_domains = ["chouti.com"] # 起始URL start_urls = [ 'http://dig.chouti.com/', ] has_request_set = {} def parse(self, response): print(response.url) hxs = HtmlXPathSelector(response) page_list = hxs.select('//div[@id="dig_lcpage"]//a[re:test(@href, "/all/hot/recent/\d+")]/@href').extract() for page in page_list: page_url = 'http://dig.chouti.com%s' % page key = self.md5(page_url) if key in self.has_request_set: pass else: self.has_request_set[key] = page_url obj = Request(url=page_url, method='GET', callback=self.parse) yield obj @staticmethod def md5(val): import hashlib ha = hashlib.md5() ha.update(bytes(val, encoding='utf-8')) key = ha.hexdigest() return key
# -*- coding: utf-8 -*- import scrapy from scrapy.selector import Selector from scrapy.http import Request class XiaohuarSpider(scrapy.Spider): name = 'xiaohuar' #爬蟲名稱 allowed_domains = ['xiaohuar.com'] #容許的域名 start_urls = ['http://www.xiaohuar.com/hua/'] #起始URL def parse(self, response): # 訪問起始URL並獲取結果後的回調函數 #處理返回的字符串,轉換成標籤 hxs = Selector(response=response) user_list = hxs.xpath('//div[@class="item masonry_brick"]') for item in user_list: price = item.xpath('.//span[@class="price"]/text()').extract_first() url = item.xpath('//div[@class="item_t"]/div[@class="img"]//a/@href').extract_first() print(price, url) # 跨頁獲取 result = hxs.xpath('.//a[re:test(@href,"http://www.xiaohuar.com/list-1-\d+.html")]/@href') print(result) # 規則,固定寫法,在配置文件中指定遞歸層數 for url in result: yield Request(url=url, callback=self.parse)
注意:Request是一個封裝用戶請求的類,請求回來都會調用參數爲 callback 的回調函數,默認爲None,咱們能夠經過callback = 函數名 指定請求回來執行某個函數。在回調函數中須要使用 yield該對象 表示才繼續訪問。在請求過程當中咱們不知道何時請求返回信息,因此咱們就須要利用回調函數去響應!
yield 的意義及在於,把當前須要繼續請求的函數交給爬蟲引擎,再次放入隊列執行。
注意:settings.py中設置DEPTH_LIMIT =n n來指定「遞歸」的層數。
4、自定義scrapy框架方法
一、自定義起始URL 執行的回調函數
當咱們建立一個爬蟲文件,都會在類下生成一個parse函數,只要是執行調用爬蟲就會默認執行這個parse函數的方法。咱們知道建立的爬蟲類繼承的是 Spider類的方法,那這裏邊是否是有相關默認執行的操做呢?咱們查看源碼發現,確實是有個函數 start_requests ,用於指定起始URL默認執行的回調函數。因此咱們能夠重寫這個函數,以實現自定義起始URL,執行咱們定義的回調函數。代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import
scrapy
from
scrapy.http
import
Request
class
ChoutiSpider(scrapy.Spider):
name
=
'chouti'
allowed_domains
=
[
'chouti.com'
]
start_urls
=
[
'http://chouti.com/'
]
def
start_requests(
self
):
"""
自定義起始url
"""
for
url
in
self
.start_urls:
yield
Request(url, dont_filter
=
True
,callback
=
self
.parse1)
def
parse1(
self
, response):
"""
自定義執行的回調函數
"""
pass
|
二、POST請求,請求頭相關問題
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
|
關於請求體數據類型的轉換:把字典類型的數據轉換成 k1
=
v1&k2
=
v2....
import
urllib.parse
data
=
urllib.parse.urlencode({
'k1'
:
'v1'
,
'k2'
:
'v2'
})
GET請求:
url,
method
=
'GET'
,
headers
=
{},
cookies
=
{}, cookiejar
POST請求:
url,
method
=
'GET'
,
headers
=
{},
cookies
=
{}, cookiejar
body
=
None
,
請求頭:application
/
x
-
www
-
form
-
urlencoded; charset
=
UTF
-
8
要傳遞數據類型:
"phone=86155fa&password=asdf&oneMonth=1"
form_data
=
{
'user'
:
'alex'
,
'pwd'
:
123
}
import
urllib.parse
data
=
urllib.parse.urlencode({
'k1'
:
'v1'
,
'k2'
:
'v2'
})
請求頭:application
/
json; charset
=
UTF
-
8
要傳遞數據類型:
"{k1:'v1','k2':'v2'}"
json.dumsp()
|
2.五、硬插一槓,獲取cookies
咱們知道在對網站請求的時候,須要攜帶cookies,那在scrapy框架返回的結果中怎麼獲取cookies呢?以下方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from
scrapy.http.cookies
import
CookieJar
#引入
#轉換成cookies的對象
cookies_jar
=
CookieJar()
#對象,內部封裝了cookies,scrapy框架能夠直接使用,打印的話須要以下轉換
cookies_jar.extract_cookies(response,response.request)
#去響應中獲取cookies
#可視化字典類型的cookies
cookie_dict
=
{}
for
k, v
in
cookies_jar._cookies.items():
for
i, j
in
v.items():
for
m, n
in
j.items():
cookie_dict[m]
=
n.value
#注意:在scrapy框架中,Request方法可使用 cookies的對象,也可使用字典類型的cookies
|
# -*- coding: utf-8 -*- import scrapy from scrapy.http import Request from scrapy.selector import Selector from scrapy.http.cookies import CookieJar import urllib.parse as urlps class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['http://chouti.com/'] #獲取cookies cookie_dict = {} def start_requests(self): """ 自定義起始url,執行訪問主頁 :return: """ for url in self.start_urls: yield Request(url, dont_filter=True,callback=self.parse_login) def parse_login(self, response): #訪問登陸頁面,登陸並拿到新的cookies cookies_jar = CookieJar() #對象,內部封裝了cookies,scrapy框架能夠直接使用,打印的話須要以下轉換 cookies_jar.extract_cookies(response,response.request) #去響應中獲取cookies for k, v in cookies_jar._cookies.items(): for i, j in v.items(): for m, n in j.items(): self.cookie_dict[m] = n.value url_info = "http://dig.chouti.com/login" post_dict = { 'phone':'8613581945998', 'password':'zyj13032025071', 'oneMonth':1, } #POST登陸 yield Request( url=url_info, method="POST", cookies=self.cookie_dict, body=urlps.urlencode(post_dict), headers={"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",}, callback=self.get_message, ) def get_message(self,response): # 獲取消息列表 print(response.text) yield Request( url="http://dig.chouti.com/", method="GET", cookies=self.cookie_dict, callback=self.parse_UP, ) def parse_UP(self,response): #找到標籤,點贊 #找div,class = part2, 獲取share-linkid print(11111) hxs = Selector(response) link_id_list = hxs.xpath('//div[@class="part2"]/@share-linkid').extract() print(link_id_list) for link_id in link_id_list: #獲取每個ID去點贊(僅在第一頁點贊) base_url = "http://dig.chouti.com/link/vote?linksId=%s"%(link_id,) yield Request( url=base_url, method="POST", cookies=self.cookie_dict, callback=self.return_infos, ) #對全部的頁面點贊 page_list = hxs.xpath('//div[@id="dig_lcpage"]//a/@href').extract() for page in page_list: page_url = "http://dig.chouti.com%s"%(page,) yield Request(url=page_url,method="GET",dont_filter=False,callback=self.parse_UP) page_list.remove(page) def return_infos(self,response): #點贊以後,獲取返回信息 print(response.text)
三、持久化
上述實例只是簡單的處理,因此能夠在parse方法中直接處理。若是對於想要獲取更多的數據處理保存在本地,則就要利用Scrapy的items將數據格式化,而後統一交由pipelines來處理持久化,保存在本地。
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
|
須要先在item.py文件中結構化,而後再經過pipeline持久化
item.py文件的類下能夠自定義字段。
class
Sp1Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
"""
注意只有Field字段
"""
name
=
scrapy.Field()
url
=
scrapy.Field()
pipeline執行的前提:
-
spider爬蟲中要
yield
Items對象
-
settings中註冊
ITEM_PIPELINES
=
{
'sp2.pipelines.Sp2Pipeline'
:
300
,
#數字表明優先級,越小優先級越大
'sp2.pipelines.Sp3Pipeline'
:
100
,
}
# 每行後面的整型值,肯定了他們運行的順序,item按數字從低到高的順序,經過pipeline,一般將這些數字定義在0-1000範圍內。
編寫pipeline 持久化操做
"""
檢測 Sp2Pipeline類中是否有 from_crawler方法:
若是有:
obj = 類.from_crawler()
若是沒有:
obj = 類()
實例化完對象以後,會調用如下方法:
obj.open_spider() #爬蟲開始執行調用
而後會循環
爬蟲運行,而且執行parse各類各樣的方法,yield item
obj.process_item() #把獲取的信息寫入文件或是數據庫
循環結束,關閉
obj.close_spider() #爬蟲關閉時調用
注意:pipeline在爬蟲開始初始化執行的時候,除了process_item()類下的其餘方法都已經執行完成【實例化對象,鏈接數據庫或是打開文件】,
而process_item()會被爬蟲代碼循環調用執行寫入信息,直至爬蟲代碼執行結束!
"""
|
# -*- coding: utf-8 -*- import scrapy from scrapy.selector import Selector from scrapy.http import Request from items import Sp1Item class JandanSpider(scrapy.Spider): name = 'jandan' allowed_domains = ['jandan.net'] start_urls = ['http://jandan.net/ooxx/'] def start_requests(self): for url in self.start_urls: yield Request(url=url,dont_filter=True,callback=self.parse1) def parse1(self, response): hxs = Selector(response) text_list = hxs.xpath('//div[@class="indexs"]/h2/a/text()').extract() for item in text_list: yield Sp1Item(text=item,name="jiandan")
import scrapy class Sp1Item(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() """ 注意只有Field字段 """ name = scrapy.Field() text = scrapy.Field()
class Sp2Pipeline(object): def __init__(self): self.f = None self.val = None def process_item(self, item, spider): """ :param item: 爬蟲中yield回來的對象 :param spider: 爬蟲對象 obj = JianDanSpider() :return: """ print(item) self.f.write('....') #將item 傳遞給下一個pipeline 的 process_item 方法 return item #把對象傳遞給下一個pipeline # 若是不想讓下一個pipline中process_item方法不執行,使用以下,拋出異常再也不執行 # from scrapy.exceptions import DropItem # raise DropItem() 下一個pipeline的process_item方法不在執行 @classmethod def from_crawler(cls, crawler): """ 初始化時候,用於建立pipeline對象 :param crawler: 爬蟲相關的全部信息,能夠去配置文件取值 :return: """ #從配置文件中取值,實例化傳給__init__方法。【通常是經過這種方式鏈接數據庫】 # val = crawler.settings.get('MMMM') print('執行pipeline的from_crawler,進行實例化對象') return cls() def open_spider(self,spider): """ 爬蟲開始執行時,調用 :param spider: 爬蟲對象 :return: """ print('打開爬蟲') #打開文件 self.f = open('a.log','a+') #鏈接數據庫 self.val = 數據庫鏈接操做 def close_spider(self,spider): """ 爬蟲關閉時,被調用 :param spider: :return: """ #關閉文件 self.f.close() #關閉數據庫
注意:
0、items結構化的文件能夠自定義編寫;可是隻有一個字段名
一、Pipeline類內最多能夠構建5個方法,以下:__init__ ,open_spider,close_spider,from_crawler,process_item。
二、PipeLine.py文件內,能夠定義多個類,可是爬蟲會順序執行不一樣類內 process_item 這個方法。
執行緣由:process_item方法下,若是最後有個 return item ,就會把對象傳遞給下一個pipeline。
#將item 傳遞給下一個pipeline 的 process_item 方法
return item #把對象傳遞給下一個pipeline
# 若是不想讓下一個pipline中process_item方法不執行,使用以下,拋出異常再也不執行
# from scrapy.exceptions import DropItem
# raise DropItem() 下一個pipeline的process_item方法不在執行
三、多個執行process_item方法的的好處就是能夠把數據保存到多地;
四、PipeLine是全局生效,全部爬蟲均可以調用執行;調用方法:【yield Item對象】。
五、若是個別爬蟲須要作特殊操做:
能夠在process_item方法下,經過spider.name獲取爬蟲的名字,經過這個名字進行判斷單獨的應用在這個爬蟲上,從而作特殊的操做!
四、自定義去重規則(避免重複訪問)
咱們都知道scrapy框架內部是有去重規則的,調用方式默認是在setting.py文件中存在的。
scrapy默認使用 scrapy.dupefilter.RFPDupeFilter 進行去重,相關配置有:
1
2
3
|
DUPEFILTER_CLASS
=
'scrapy.dupefilter.RFPDupeFilter'
DUPEFILTER_DEBUG
=
False
JOBDIR
=
"保存範文記錄的日誌路徑,如:/root/"
# 最終路徑爲 /root/requests.seen
|
咱們自定義的話,須要先在當前項目下建立一個py文件,剩下的就兩點:
- 定義一個類;
- 配置文件中指定
DUPEFILTER_CLASS = 'sp2.rep.RepeatUrl' #文件的相對路徑+類名!
class RepeatUrl: def __init__(self): #能夠把要檢測的url寫在數據庫 or 放在另外一臺服務器的內存,或是緩存 self.visited_url = set()#結合 # @classmethod def from_settings(cls, settings): """ 初始化時,調用 :param settings:配置文件,能夠取東西 :return: """ return cls() def request_seen(self, request): """ 檢測當前請求是否已經被訪問過 :param request:請求 :return: True表示已經訪問過;False表示未訪問過 """ if request.url in self.visited_url: return True self.visited_url.add(request.url) return False def open(self): """ 開始爬去請求時,調用 :return: """ print('open replication') def close(self, reason): """ 結束爬蟲爬取時,調用 :param reason: :return: """ print('close replication') def log(self, request, spider): """ 記錄日誌 :param request: :param spider: :return: """ print('repeat', request.url)
五、自定義擴展【基於信號】
自定義擴展時,利用信號在指定位置註冊制定操做。
應用的話有兩點:
一、須要在當前項目下建立一個py文件。在文件中寫入自定義擴展的操做。
二、須要在配置文件中註冊
EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
# '文件路徑': 數字表明優先級,
}
from scrapy import signals class MyExtension(object): def __init__(self, value): self.value = value @classmethod def from_crawler(cls, crawler): val = crawler.settings.getint('MMMM') ext = cls(val) # 在scrapy中註冊信號: ext.opened 註冊觸發信號時執行的函數,爬蟲執行自動觸發 crawler.signals.connect(ext.opened, signal=signals.spider_opened) # 在scrapy中註冊信號: 同上 crawler.signals.connect(ext.closed, signal=signals.spider_closed) return ext #自定義觸發信號時執行的函數 def opened(self, spider): print('open') def closed(self, spider): print('close')
應用:在執行爬蟲,若想實現其餘的功能或是操做,不用多想,此時就須要自定義擴展!
六、中間件
咱們在學習Django框架已經學習了中間件,爬蟲的中間件功能與其一致,也是爲了實現批量處理。爬蟲的中間件分爲:爬蟲中間件(做用於爬蟲文件與引擎之間)、下載中間件(做用於引擎與下載器之間)。在中間件中咱們能夠自定義方法,對全部要執行的爬蟲或是在下載以前,進行處理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
-
下載中間件
在process_request方法中,能夠修改 請求方法 或是 請求頭信息;加入代理。
注意:咱們能夠在這個方法內,自定義請求地址 獲取下載的信息,而後放入response響應體中,無論爬蟲爬取的是哪一個網站的數據返回的結果都是咱們自定義的下載信息。
在process_response方法中,能夠自定義響應信息的處理方式。
-
引用的話,須要在配置文件中註冊:
# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES
=
{
'sp2.middlewares.Sp2SpiderMiddleware'
:
543
,
}
-
爬蟲中間件
-
start_requests 可迭代對象
-
引用的話,須要在配置文件中註冊:
# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES
=
{
'sp2.middlewares.MyCustomDownloaderMiddleware'
:
543
,
}
返回的響應信息中,存在發起請求的request信息,獲取的方法:response.request
|
class SpiderMiddleware(object): def process_spider_input(self,response, spider): """ 下載完成,執行,而後交給parse處理 :param response: :param spider: :return: """ pass def process_spider_output(self,response, result, spider): """ spider處理完成,返回時調用 :param response: :param result: :param spider: :return: 必須返回包含 Request 或 Item 對象的可迭代對象(iterable) """ return result def process_spider_exception(self,response, exception, spider): """ 異常調用 :param response: :param exception: :param spider: :return: None,繼續交給後續中間件處理異常;含 Response 或 Item 的可迭代對象(iterable),交給調度器或pipeline """ return None def process_start_requests(self,start_requests, spider): """ 爬蟲啓動時調用 :param start_requests: :param spider: :return: 包含 Request 對象的可迭代對象 """ return start_requests
class DownMiddleware1(object): def process_request(self, request, spider): """ 請求須要被下載時,通過全部下載器中間件的process_request調用 :param request: :param spider: :return: None,繼續後續中間件去下載; Response對象,中止process_request的執行,開始執行process_response Request對象,中止中間件的執行,將Request從新調度器 raise IgnoreRequest異常,中止process_request的執行,開始執行process_exception """ """ #自定義請求方式,添加或是修改請求頭 from scrapy.http import Request # print(request) # request.method = "POST" request.headers['proxy'] = "{'ip_port': '111.11.228.75:80', 'user_pass': ''}," return None """ """ #自定義請求地址,返回響應信息 from scrapy.http import Response import requests v = request.get('http://www.baidu.com') data = Response(url='xxxxxxxx',body=v.content,request=request) return data """ pass def process_response(self, request, response, spider): """ spider處理完成,返回時調用 :param response: :param result: :param spider: :return: Response 對象:轉交給其餘中間件process_response Request 對象:中止中間件,request會被從新調度下載 raise IgnoreRequest 異常:調用Request.errback """ print('response1') #在響應回來時,咱們能夠對響應體進行某些處理 # from scrapy.http import Response # response.encoding = 'utf-8' return response def process_exception(self, request, exception, spider): """ 當下載處理器(download handler)或 process_request() (下載中間件)拋出異常 :param response: :param exception: :param spider: :return: None:繼續交給後續中間件處理異常; Response對象:中止後續process_exception方法 Request對象:中止中間件,request將會被從新調用下載 """ return None
七、自定義命令
- 讓全部爬蟲都開始工做
from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings class Command(ScrapyCommand): requires_project = True def syntax(self): return '[options]' def short_desc(self): return 'Runs all of the spiders' def run(self, args, opts): # 爬蟲列表 spider_list = self.crawler_process.spiders.list() for name in spider_list: # 初始化爬蟲 self.crawler_process.crawl(name, **opts.__dict__) # 開始執行全部的爬蟲 self.crawler_process.start()
八、scrapy 配置文件
# Scrapy settings for step8_king project # # For simplicity, this file contains only settings considered important or # commonly used. You can find more settings consulting the documentation: # # http://doc.scrapy.org/en/latest/topics/settings.html # http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html # http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html # 1. 爬蟲名稱 BOT_NAME = 'step8_king' # 2. 爬蟲應用路徑 SPIDER_MODULES = ['step8_king.spiders'] NEWSPIDER_MODULE = 'step8_king.spiders' # Crawl responsibly by identifying yourself (and your website) on the user-agent # 3. 客戶端 user-agent請求頭 # USER_AGENT = 'step8_king (+http://www.yourdomain.com)' # Obey robots.txt rules # 4. 禁止爬蟲配置 # ROBOTSTXT_OBEY = False # Configure maximum concurrent requests performed by Scrapy (default: 16) # 5. 併發請求數 # CONCURRENT_REQUESTS = 4 # Configure a delay for requests for the same website (default: 0) # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay # See also autothrottle settings and docs # 6. 延遲下載秒數 # DOWNLOAD_DELAY = 2 # The download delay setting will honor only one of: # 7. 單域名訪問併發數,而且延遲下次秒數也應用在每一個域名 # CONCURRENT_REQUESTS_PER_DOMAIN = 2 # 單IP訪問併發數,若是有值則忽略:CONCURRENT_REQUESTS_PER_DOMAIN,而且延遲下次秒數也應用在每一個IP # CONCURRENT_REQUESTS_PER_IP = 3 # Disable cookies (enabled by default) # 8. 是否支持cookie,cookiejar進行操做cookie # COOKIES_ENABLED = True # COOKIES_DEBUG = True # Disable Telnet Console (enabled by default) # 9. Telnet用於查看當前爬蟲的信息,操做爬蟲等... # 使用telnet ip port ,而後經過命令操做 # TELNETCONSOLE_ENABLED = True # TELNETCONSOLE_HOST = '127.0.0.1' # TELNETCONSOLE_PORT = [6023,] # 10. 默認請求頭 # Override the default request headers: # DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', # } # Configure item pipelines # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html # 11. 定義pipeline處理請求 # ITEM_PIPELINES = { # 'step8_king.pipelines.JsonPipeline': 700, # 'step8_king.pipelines.FilePipeline': 500, # } # 12. 自定義擴展,基於信號進行調用 # Enable or disable extensions # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html # EXTENSIONS = { # # 'step8_king.extensions.MyExtension': 500, # } # 13. 爬蟲容許的最大深度,能夠經過meta查看當前深度;0表示無深度 # DEPTH_LIMIT = 3 # 14. 爬取時,0表示深度優先Lifo(默認);1表示廣度優先FiFo # 後進先出,深度優先 # DEPTH_PRIORITY = 0 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue' # 先進先出,廣度優先 # DEPTH_PRIORITY = 1 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue' # 15. 調度器隊列 # SCHEDULER = 'scrapy.core.scheduler.Scheduler' # from scrapy.core.scheduler import Scheduler # 16. 訪問URL去重 # DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl' # Enable and configure the AutoThrottle extension (disabled by default) # See http://doc.scrapy.org/en/latest/topics/autothrottle.html """ 17. 自動限速算法 from scrapy.contrib.throttle import AutoThrottle 自動限速設置 1. 獲取最小延遲 DOWNLOAD_DELAY 2. 獲取最大延遲 AUTOTHROTTLE_MAX_DELAY 3. 設置初始下載延遲 AUTOTHROTTLE_START_DELAY 4. 當請求下載完成後,獲取其"鏈接"時間 latency,即:請求鏈接到接受到響應頭之間的時間 5. 用於計算的... AUTOTHROTTLE_TARGET_CONCURRENCY target_delay = latency / self.target_concurrency new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延遲時間 new_delay = max(target_delay, new_delay) new_delay = min(max(self.mindelay, new_delay), self.maxdelay) slot.delay = new_delay """ # 開始自動限速 # AUTOTHROTTLE_ENABLED = True # The initial download delay # 初始下載延遲 # AUTOTHROTTLE_START_DELAY = 5 # The maximum download delay to be set in case of high latencies # 最大下載延遲 # AUTOTHROTTLE_MAX_DELAY = 10 # The average number of requests Scrapy should be sending in parallel to each remote server # 平均每秒併發數 # AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # Enable showing throttling stats for every response received: # 是否顯示 # AUTOTHROTTLE_DEBUG = True # Enable and configure HTTP caching (disabled by default) # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings """ 18. 啓用緩存 目的用於將已經發送的請求或相應緩存下來,以便之後使用【沒有網絡,可是數據已經緩存下來,能夠測試使用】 from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware from scrapy.extensions.httpcache import DummyPolicy from scrapy.extensions.httpcache import FilesystemCacheStorage """ # 是否啓用緩存策略 # HTTPCACHE_ENABLED = True # 緩存策略:全部請求均緩存,下次在請求直接訪問原來的緩存便可 # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy" # 緩存策略:根據Http響應頭:Cache-Control、Last-Modified 等進行緩存的策略 # HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy" # 緩存超時時間 # HTTPCACHE_EXPIRATION_SECS = 0 # 緩存保存路徑 # HTTPCACHE_DIR = 'httpcache' # 緩存忽略的Http狀態碼 # HTTPCACHE_IGNORE_HTTP_CODES = [] # 緩存存儲的插件 # HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
""" 19. 代理,須要在環境變量中設置 from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware 方式一:使用默認 os.environ { http_proxy:http://root:woshiniba@192.168.11.11:9999/ https_proxy:http://192.168.11.11:9999/ } 例如:咱們能夠在自定義命令中引入,能夠在爬蟲執行的時候,使用代理IP。 from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware import os os.environ['http_proxy'] = "http://root:woshiniba@192.168.11.11:9999/" os.environ['https_proxy'] = "http://192.168.11.11:9999/" os.environ['xx_proxy'] = "http://192.168.11.11:9999/" 方式二:使用自定義下載中間件 def to_bytes(text, encoding=None, errors='strict'): if isinstance(text, bytes): return text if not isinstance(text, six.string_types): raise TypeError('to_bytes must receive a unicode, str or bytes ' 'object, got %s' % type(text).__name__) if encoding is None: encoding = 'utf-8' return text.encode(encoding, errors) class ProxyMiddleware(object): def process_request(self, request, spider): PROXIES = [ {'ip_port': '111.11.228.75:80', 'user_pass': ''}, {'ip_port': '120.198.243.22:80', 'user_pass': ''}, {'ip_port': '111.8.60.9:8123', 'user_pass': ''}, {'ip_port': '101.71.27.120:80', 'user_pass': ''}, {'ip_port': '122.96.59.104:80', 'user_pass': ''}, {'ip_port': '122.224.249.122:8088', 'user_pass': ''}, ] proxy = random.choice(PROXIES) if proxy['user_pass'] is not None: request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass'])) request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass) print "**************ProxyMiddleware have pass************" + proxy['ip_port'] else: print "**************ProxyMiddleware no pass************" + proxy['ip_port'] request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) # 配置文件中註冊 DOWNLOADER_MIDDLEWARES = { 'step8_king.middlewares.ProxyMiddleware': 500, } """
""" 20. Https訪問 Https訪問時有兩種狀況: 1. 要爬取網站使用的可信任證書(默認支持) DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory" 2. 要爬取網站使用的自定義證書 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory" # https.py from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) class MySSLFactory(ScrapyClientContextFactory): def getCertificateOptions(self): from OpenSSL import crypto v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read()) v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read()) return CertificateOptions( privateKey=v1, # pKey對象 certificate=v2, # X509對象 verify=False, method=getattr(self, 'method', getattr(self, '_ssl_method', None)) ) 其餘: 相關類 scrapy.core.downloader.handlers.http.HttpDownloadHandler scrapy.core.downloader.webclient.ScrapyHTTPClientFactory scrapy.core.downloader.contextfactory.ScrapyClientContextFactory 相關配置 DOWNLOADER_HTTPCLIENTFACTORY DOWNLOADER_CLIENTCONTEXTFACTORY """
""" 21. 爬蟲中間件 class SpiderMiddleware(object): def process_spider_input(self,response, spider): ''' 下載完成,執行,而後交給parse處理 :param response: :param spider: :return: ''' pass def process_spider_output(self,response, result, spider): ''' spider處理完成,返回時調用 :param response: :param result: :param spider: :return: 必須返回包含 Request 或 Item 對象的可迭代對象(iterable) ''' return result def process_spider_exception(self,response, exception, spider): ''' 異常調用 :param response: :param exception: :param spider: :return: None,繼續交給後續中間件處理異常;含 Response 或 Item 的可迭代對象(iterable),交給調度器或pipeline ''' return None def process_start_requests(self,start_requests, spider): ''' 爬蟲啓動時調用 :param start_requests: :param spider: :return: 包含 Request 對象的可迭代對象 ''' return start_requests 內置爬蟲中間件: 'scrapy.contrib.spidermiddleware.httperror.HttpErrorMiddleware': 50, 'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': 500, 'scrapy.contrib.spidermiddleware.referer.RefererMiddleware': 700, 'scrapy.contrib.spidermiddleware.urllength.UrlLengthMiddleware': 800, 'scrapy.contrib.spidermiddleware.depth.DepthMiddleware': 900, """ # from scrapy.contrib.spidermiddleware.referer import RefererMiddleware # Enable or disable spider middlewares # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html SPIDER_MIDDLEWARES = { # 'step8_king.middlewares.SpiderMiddleware': 543, } """ 22. 下載中間件 class DownMiddleware1(object): def process_request(self, request, spider): ''' 請求須要被下載時,通過全部下載器中間件的process_request調用 :param request: :param spider: :return: None,繼續後續中間件去下載; Response對象,中止process_request的執行,開始執行process_response Request對象,中止中間件的執行,將Request從新調度器 raise IgnoreRequest異常,中止process_request的執行,開始執行process_exception ''' pass def process_response(self, request, response, spider): ''' spider處理完成,返回時調用 :param response: :param result: :param spider: :return: Response 對象:轉交給其餘中間件process_response Request 對象:中止中間件,request會被從新調度下載 raise IgnoreRequest 異常:調用Request.errback ''' print('response1') return response def process_exception(self, request, exception, spider): ''' 當下載處理器(download handler)或 process_request() (下載中間件)拋出異常 :param response: :param exception: :param spider: :return: None:繼續交給後續中間件處理異常; Response對象:中止後續process_exception方法 Request對象:中止中間件,request將會被從新調用下載 ''' return None 默認下載中間件 { 'scrapy.contrib.downloadermiddleware.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware': 300, 'scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 400, 'scrapy.contrib.downloadermiddleware.retry.RetryMiddleware': 500, 'scrapy.contrib.downloadermiddleware.defaultheaders.DefaultHeadersMiddleware': 550, 'scrapy.contrib.downloadermiddleware.redirect.MetaRefreshMiddleware': 580, 'scrapy.contrib.downloadermiddleware.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.contrib.downloadermiddleware.redirect.RedirectMiddleware': 600, 'scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware': 700, 'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 750, 'scrapy.contrib.downloadermiddleware.chunked.ChunkedTransferMiddleware': 830, 'scrapy.contrib.downloadermiddleware.stats.DownloaderStats': 850, 'scrapy.contrib.downloadermiddleware.httpcache.HttpCacheMiddleware': 900, } """ # from scrapy.contrib.downloadermiddleware.httpauth import HttpAuthMiddleware # Enable or disable downloader middlewares # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html # DOWNLOADER_MIDDLEWARES = { # 'step8_king.middlewares.DownMiddleware1': 100, # 'step8_king.middlewares.DownMiddleware2': 500, # }
補充:
爬蟲也支持telnet終端操做,telnet 鏈接上 正在處理的爬蟲,能夠經過est() 查看正在執行的全部操做列表。
1
|
telnet localhost
6023
est()
|
更對文檔請參考:官方文檔