scrapy簡介
Scrapy 使用了 Twisted異步網絡庫來處理網絡通訊。整體架構大致如下
Scrapy主要包括了以下組件:
- 引擎(Scrapy)
用來處理整個系統的數據流處理, 觸發事務(框架核心) - 調度器(Scheduler)
用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 可以想像成一個URL(抓取網頁的網址或者說是鏈接)的優先隊列, 由它來決定下一個要抓取的網址是什么, 同時去除重復的網址 - 下載器(Downloader)
用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是建立在twisted這個高效的異步模型上的) - 爬蟲(Spiders)
爬蟲是主要干活的, 用於從特定的網頁中提取自己需要的信息, 即所謂的實體(Item)。用戶也可以從中提取出鏈接,讓Scrapy繼續抓取下一個頁面 - 項目管道(Pipeline)
負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證實體的有效性、清除不需要的信息。當頁面被爬蟲解析后,將被發送到項目管道,並經過幾個特定的次序處理數據。 - 下載器中間件(Downloader Middlewares)
位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。 - 爬蟲中間件(Spider Middlewares)
介於Scrapy引擎和爬蟲之間的框架,主要工作是處理蜘蛛的響應輸入和請求輸出。 - 調度中間件(Scheduler Middewares)
介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。
Scrapy運行流程大概如下:
- 引擎從調度器中取出一個鏈接(URL)用於接下來的抓取
- 引擎把URL封裝成一個請求(Request)傳給下載器
- 下載器把資源下載下來,並封裝成應答包(Response)
- 爬蟲解析Response
- 解析出實體(Item),則交給實體管道進行進一步的處理
- 解析出的是鏈接(URL),則把URL交給調度器等待抓取
安裝
Linux下的安裝(包括mac)
pip install scrapy
Windows下的安裝
http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
b. 安裝wheel
pip3 install wheel
c. 安裝twisted
進入下載目錄,執行
pip3 install Twisted‑18.7.0‑cp36‑cp36m‑win_amd64.whl
d. 安裝pywin32
pip3 install pywin32
e. 安裝scrapy
pip3 install scrapy
基本命令
1.
scrapy startproject 項目名稱
-
在當前目錄中創建一個項目文件(類似於Django)
2.
scrapy genspider [
-
t template] <name> <domain>
-
創建爬蟲應用
如:
scrapy gensipider
-
t basic oldboy oldboy.com
scrapy gensipider
-
t xmlfeed autohome autohome.com.cn
PS:
查看所有命令:scrapy gensipider
-
l
查看模板命令:scrapy gensipider
-
d 模板名稱
3.
scrapy
list
-
展示爬蟲應用列表
4.
scrapy crawl 爬蟲應用名稱
-
運行單獨爬蟲應用
備注:scrapy crawl 應用名稱 表示以日志的形式運行爬蟲應用,可以在后面加 --nolog 取消日志 scrapy crawl 名稱 --nolog
項目結構以及爬蟲應用簡介
1
2
3
4
5
6
7
8
9
10
11
12
|
project_name
/
scrapy.cfg
project_name
/
__init__.py
items.py
middlewares.py
pipelines.py
settings.py
spiders
/
__init__.py
爬蟲
1.py
爬蟲
2.py
爬蟲
3.py
|
文件說明:
- scrapy.cfg 項目的主配置信息。(真正爬蟲相關的配置信息在settings.py文件中)
- items.py 設置數據存儲模板,用於結構化數據,如:Django的Model
- pipelines 數據處理行為,如:一般結構化的數據持久化
- settings.py 配置文件,如:遞歸的層數、並發數,延遲下載等
- spiders 爬蟲目錄,如:創建文件,編寫爬蟲規則
注意:一般創建爬蟲文件時,以網站域名命名
import sys,io sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')

import scrapy class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/'] def parse(self, response): pass

在爬取數據時,可以選擇是否往.../robots.txt/發送驗證,是否允許爬取,一般設置為False

使用scrapy解析文本內容時,可以使用每個應用中的response.xpath(xxx) 進行數據的解析。
Selector對象。selector對象可以繼續xpath進行數據的解析。
1.//+標簽 表示從全局的子子孫孫中查找標簽
response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8') hxs = HtmlXPathSelector(response) print(hxs) # selector對象 hxs = Selector(response=response).xpath('//a') print(hxs) #查找所有的a標簽 hxs = Selector(response=response).xpath('//a[2]') print(hxs) #查找某一個具體的a標簽 取第三個a標簽 hxs = Selector(response=response).xpath('//a[@id]') print(hxs) #查找所有含有id屬性的a標簽 hxs = Selector(response=response).xpath('//a[@id="i1"]') print(hxs) # 查找含有id=“i1”的a標簽 # hxs = Selector(response=response).xpath('//a[@href="link.html"][@id="i1"]') # print(hxs) # 查找含有href=‘xxx’並且id=‘xxx’的a標簽 # hxs = Selector(response=response).xpath('//a[contains(@href, "link")]') # print(hxs) # 查找 href屬性值中包含有‘link’的a標簽 # hxs = Selector(response=response).xpath('//a[starts-with(@href, "link")]') # print(hxs) # 查找 href屬性值以‘link’開始的a標簽 # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]') # print(hxs) # 正則匹配的用法 匹配id屬性的值為數字的a標簽 # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/text()').extract() # print(hxs) # 匹配id屬性的值為數字的a標簽的文本內容 # hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/@href').extract() # print(hxs) #匹配id屬性的值為數字的a標簽的href屬性值 # 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的持久化
scrapy的持久化過程分為四個部分
首先,items定義傳輸的格式,其次,在爬蟲應用中yield這個item對象,pipeline收到yield的item對象,進行持久化操作,這個過程中,settings中要進行相應的配置
items.py
# 規范持久化的格式 import scrapy class MyspiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() url=scrapy.Field()
爬蟲應用
import scrapy from myspider.items import MyspiderItem class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/'] def parse(self, response): # print(response.text) a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract() for url in a_list: yield MyspiderItem(url=url)
pipelines.py
class MyspiderPipeline(object): def __init__(self,file_path): self.f = None self.file_path = file_path @classmethod def from_crawler(cls,crawler): ''' 執行pipeline類時,會先去類中找from_crawler的方法, 如果有,則先執行此方法,並且返回一個當前類的對象, 如果沒有,則直接執行初始化方法 :param crawler: :return: ''' # 可以進行一些初始化之前的處理,比如:文件的路徑配置到settings文件中,方便后期的更改。 file_path = crawler.settings.get('PACHONG_FILE_PATH') return cls(file_path) def open_spider(self,spider): ''' 爬蟲開始時被調用 :param spider: :return: ''' self.f = open(self.file_path,'w',encoding='utf8') def process_item(self, item, spider): ''' 執行持久化的邏輯操作 :param item: 爬蟲yield過來的item對象 (一個字典) :param spider: 爬蟲對象 :return: ''' self.f.write(item['url']+'\n') self.f.flush() #將寫入到內存的文件強刷到文件中,防止夯住,不使用此方法會夯住 return item def close_spider(self,spider): ''' 爬蟲結束時調用 :param spider: :return: ''' self.f.close()
備注:執行pipeline時,會先找from_crawler方法,這個方法中,我們可以設置一些settings文件中的配置,通過crawler.settings得到一個settings對象(配置文件對象) <scrapy.settings.Settings object at 0x000002525581F908>
執行pipeline中的process_item() 方法進行數據的持久化處理時,如果有多個pipeline(比如:將數據分別寫入文件和數據庫)時,先執行的pipeline(按照配置文件中數值的大小順序執行),必須返回一個item對象,否則,后續的pipeline執行時,接收的item為None,無法進行數據的持久化操作,如果只是單純的對某些數據進行一個持久化的處理,可以通過拋出異常,來阻止當前item對象后續的pipeline執行。拋出異常為:from scrapy.exceptions import DropItem 直接 raise DropItem()
return不返回item對象與拋異常的區別:無返回值或者返回值為None時,后續的pipeline會執行,只是,此時的item為None,而拋出異常,會跳過當前對象后續的pipeline,執行下一個item對象。
settings.py
ITEM_PIPELINES = {
'myspider.pipelines.MyspiderPipeline': 300,
'xxxxx.pipelines.FilePipeline': 400,
} # 每行后面的整型值,確定了他們運行的順序,item按數字從低到高的順序,通過pipeline,通常將這些數字定義在0-1000范圍內。
備注:數值小的先執行。
獲取所有頁面
import scrapy from myspider.items import MyspiderItem from scrapy.http import Request class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/'] def parse(self, response): a_list = response.xpath('//div[@id="content-list"]//div[@class="part1"]/a[@class="show-content color-chag"]/@href').extract() for url in a_list: yield MyspiderItem(url=url) # 獲取分頁的url url_list = response.xpath('//div[@id="dig_lcpage"]//a/@href').extract() for url in url_list: url = 'https://dig.chouti.com%s'%url yield Request(url=url,callback=self.parse)
備注:通過yield 每一個request對象,將所有的頁面url添加到調度器中。
scrapy框架會默認的將所有的結果進行去重操作。如果不去重,可以在request參數中,設置 dont_filter=True
class Spider(object_ref):
def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True)
備注:在執行爬蟲應用時,會先執行start_requests方法,所以我們可以重寫此方法自定制。
獲取響應數據中的cookie
返回的response中,無法通過 .cookies 獲取cookie,只能通過從響應頭中獲取,但是獲取的結果還得需要解析。
{b'Server': [b'Tengine'], b'Content-Type': [b'text/html; charset=UTF-8'], b'Date': [
b'Fri, 20 Jul 2018 13:43:42 GMT'], b'Cache-Control': [b'private'], b'Content-Language': [b'en'],
b'Set-Cookie': [b'gpsd=5b05bcae8c6f4a273a53addfc8bbff22; domain=chouti.com; path=/; expires=Sun,
19-Aug-2018 13:43:42 GMT', b'JSESSIONID=aaadbrXmU-Jh2_kvbaysw; path=/'], b'Vary': [b'Accept-Encoding'],
b'Via': [b'cache15.l2nu29-1[69,0], kunlun9.cn125[73,0]'], b'Timing-Allow-Origin': [b'*'],
b'Eagleid': [b'6a78b50915320942226762320e']}
所以,要通過scrapy封裝的方法,將cookie解析出來
import scrapy from scrapy.http.cookies import CookieJar class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/'] cookie_dict = {} def parse(self, response): cookie_jar = CookieJar() cookie_jar.extract_cookies(response,response.request) for k, v in cookie_jar._cookies.items(): for i, j in v.items(): for m, n in j.items(): self.cookie_dict[m] = n.value print(self.cookie_dict)
備注:CookieJar中封裝的內容特別豐富,print(cookie_jar._cookies) 包含很多
{'.chouti.com': {'/': {'gpsd': Cookie(version=0, name='gpsd', value='fcb9b9da7aaede0176d2a88cde8b6adb',
port=None, port_specified=False, domain='.chouti.com', domain_specified=True, domain_initial_dot=False,
path='/', path_specified=True, secure=False, expires=1534688487, discard=False, comment=None,
comment_url=None, rest={}, rfc2109=False)}}, 'dig.chouti.com': {'/': {'JSESSIONID':
Cookie(version=0, name='JSESSIONID', value='aaa4GWMivXwJf6ygMaysw', port=None, port_specified=False,
domain='dig.chouti.com', domain_specified=False, domain_initial_dot=False, path='/',
path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={},
rfc2109=False)}}}
自動登錄抽屜並點贊和取消贊代碼示例

import scrapy from scrapy.http.response.html import HtmlResponse # import sys,os,io # sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030') from ..items import XzxItem from scrapy.http import Request from scrapy.http.cookies import CookieJar class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/r/ask/hot/1'] cookie_dict = {} def start_requests(self): for url in self.start_urls: yield Request(url=url,callback=self.parse) def parse(self, response): # 1. 去第一次訪問頁面中獲取cookie # print(response.headers['Set-Cookie'],type(response.headers['Set-Cookie'])) cookie_jar = CookieJar() # 空 cookie_jar.extract_cookies(response, response.request) # cookie_jar中包含了cookie for k, v in cookie_jar._cookies.items(): for i, j in v.items(): for m, n in j.items(): self.cookie_dict[m] = n.value # 2. 向https://dig.chouti.com/login發送POST請求 yield Request( url='https://dig.chouti.com/login', method='POST', body="phone=8615901492719&password=qwer1234&oneMonth=1", cookies=self.cookie_dict, headers={ 'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', 'content-type':'application/x-www-form-urlencoded; charset=UTF-8', }, callback=self.check_login ) def check_login(self,response): print(response.text) yield Request(url='https://dig.chouti.com/',callback=self.index) def index(self,response): news_id_list = response.xpath('//div[@id="content-list"]//div[@class="part2"]/@share-linkid').extract() for news_id in news_id_list: # 贊 """ news_url = "https://dig.chouti.com/link/vote?linksId=%s" %(news_id,) yield Request( url=news_url, method='POST', cookies=self.cookie_dict, callback=self.output ) """ # 取消贊 news_url = "https://dig.chouti.com/vote/cancel/vote.do" yield Request( url=news_url, method='POST', body="linksId=%s" %(news_id,), cookies=self.cookie_dict, headers={ 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', }, callback=self.output ) # 2.1 獲取頁面 page_list = response.xpath('//*[@id="dig_lcpage"]//a/@href').extract() for url in page_list: url = "https://dig.chouti.com" + url yield Request(url=url,callback=self.index) def output(self,response): print(response.text)
備注:爬取過程中的坑:請求頭中,一定要攜帶content-type參數。請求過程中的url不能重復,尤其是和起始url。
from urllib.parse import urlencode ret = {'name':'xxx','age':18} print(urlencode(ret))