第一步:首先Spiders(爬蟲)將需要發送請求的url(request)經過ScrapyEngine(引擎)交給Scheduler(調度器).
第二步:Scheduler(排序,入隊)處理后,經過ScrapyEngine,DownloaderMiddlewares(可選,主要有User_Agent,Proxy代理)交給Downloader.
第三步:Downloader向互聯網發送請求,斌接受下載響應(response)。將響應(response)經過ScrapyEngine,SpiderMiddlewares(可選)交給Spiders.
第四步:Spiders處理response,提取數據並將數據經過ScrapyEngine交給ItemPipeline保存(可以是本地,可以是數據庫)。循環,提取url重新經過ScrapyEngine交給Scheduler進行下一個循環。知道無Url請求程序停止結束。直到無Url請求程序停止結束
項目結構以及爬蟲引用簡介
project_name
/
scrapy.cfg
project_name
/
__init__.py
items.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 爬蟲目錄,如:創建文件,編寫爬蟲規則
使用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)
備注:xpath中支持正則的使用: 用法 標簽+[re:test(@屬性值,"正則表達式")]
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對象。
setting.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)}}}
備注:爬取過程中的坑:請求頭中,一定要攜帶content-type參數。請求過程中的url不能重復,尤其是和起始url。
我們可以使用urllib中的urlencode幫我們把數據轉化為formdata格式的.
from urllib.parse import urlencode ret = {'name':'xxx','age':18} print(urlencode(ret))