scrapy數據建模與請求
學習目標:
- 應用 在scrapy項目中進行建模
- 應用 構造Request對象,并發送請求
- 應用 利用meta參數在不同的解析函數中傳遞數據
- scrapy構造post請求
1. 數據建模
通常在做項目的過程中,在items.py中進行數據建模
1.1 為什么建模
- 定義item即提前規劃好哪些字段需要抓,防止手誤,因為定義好之后,在運行過程中,系統會自動檢查
- 配合注釋一起可以清晰的知道要抓取哪些字段,沒有定義的字段不能抓取,在目標字段少的時候可以使用字典代替
1.2 如何建模
在items.py文件中定義要提取的字段:
# Define here the models for your scraped items
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.htmlimport scrapy
class DoubanItem(scrapy.Item):# define the fields for your item here like:name = scrapy.Field() # 名字content = scrapy.Field() # 內容link = scrapy.Field() # 鏈接txt = scrapy.Field() #詳情介紹
1.3 如何使用模板類
模板類定義以后需要在爬蟲中導入并且實例化,之后的使用方法和使用字典相同
爬蟲文件.py
# -*- coding:utf-8 -*-
import scrapy
from douban.items import DoubanItem
....def parse(self, response):name = response.xpath('//h2[@class="clearfix"]/a/text()').extract()content = response.xpath('//p[@class="subject-abstract color-gray"]/text()').extract()link = response.xpath('//h2[@class="clearfix"]/a/@href').extract()for names, contents, links in zip(name, content, link):item = DoubanItem() # 實例化后拿到模板類就可直接使用 本質是一個字典item['name'] = namesitem['content'] = contents.strip()item['link'] = links
注意:
1. from douban.items import DoubanItem這一行代碼中 注意item的正確導入路徑,忽略pycharm標記的錯誤
2. python中的導入路徑要訣:從哪里開始運行,就從哪里開始導入
1.4 開發流程總結
1. 創建項目scrapy startproject 項目名
2. 明確目標在items.py文件中進行建模 (一般來說在開發流程里建模是必須的,但如果字段特別少也可以選擇忽略)
3. 創建爬蟲3.1 創建爬蟲scrapy genspider 爬蟲名 允許的域3.2 完成爬蟲修改start_urls檢查修改allowed_domains在parse方法里編寫解析方法
4. 保存數據在pipelines.py文件中定義對數據處理的管道在settings.py文件中注冊啟用管道
2. 翻頁請求的思路
- 找到下一頁的url地址
- 把url地址構造成請求對象,傳遞給引擎
3. 構造Request對象,并發送請求
3.1 實現方法
- 確定url地址
- 構造請求,scrapy.Request(url,callback)
- callback:指定響應體解析的函數名稱,表示該請求返回的響應使用哪一個函數進行解析(callback不賦值的話默認是給parse方法解析)
- 把請求交給引擎:yield scrapy.Request(url,callback)
3.2 豆瓣新書速遞爬蟲
通過爬取豆瓣新書速遞的頁面信息,學習如何實現翻頁請求
地址:豆瓣新書速遞
思路分析:
- 獲取首頁的響應數據(因為里面有我們想要的翻頁鏈接)
- 尋找下一頁的地址,進行翻頁,獲取數據
注意:
-
可以在settings中設置ROBOTS協議
False表示忽略網站的robots.txt協議,默認為True
ROBOTSTXT_OBEY = False
-
可以在settings中設置User-Agent:
(scrapy發送的每一個請求的默認UA都是設置的這個User-Agent)USER_AGENT = ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36’
3.3 代碼實現
在爬蟲文件的parse方法中:
....# 1,構造翻頁# 提取下一頁urlpart_url = response.xpath('//span[@class="next"]//a/@href').extract_first()# 2,判斷是否為下一頁的條件if '?subcat=' in part_url:# 構造完整的urlnext_url = response.urljoin(part_url)print("下一頁參數信息:", part_url)print("下一頁鏈接:", next_url)# 構造scrapy.Request對象,并yield給引擎,利用callback參數指定該Request對象之后獲取的響應用哪個函數進行解析 yield scrapy.Request(url=next_url, callback=self.parse)
3.4 scrapy.Request的更多參數
scrapy.Request(url[callback,method=“GET”,headers,body,cookies,meta,dont_filter=False])
參數解釋
- 中括號里的參數為可選參數
- callback:表示當前的url的響應交給哪個函數去處理
- meta:實現數據在不同的解析函數中傳遞,meta默認帶有部分數據,比如下載延遲,請求深度等
- dont_filter:默認為False,會過濾請求的url地址,即請求過的url地址不會繼續被請求,對需要重復請求的url地址可以把它設置為Ture,比如貼吧的翻頁請求,頁面的數據總是在變化;start_urls中的地址會被反復請求,否則程序不會啟動
- method:指定POST或GET請求
- headers:接收一個字典,其中不包括cookies
- cookies:接收一個字典,專門放置cookies
- body:接收json字符串,為POST的數據,發送payload_post請求時使用(在下面內容中會介紹post請求)
4. meta參數的使用
meta的作用:meta可以實現數據在不同的解析函數中的傳遞
使用場景:常用在數據分散在不同結構頁面中
在爬蟲文件的parse方法中,提取詳情頁增加之前callback指定的parse_detail函數:
# 爬蟲默認自帶的解析方法
def parse(self,response):yield scrapy.Request(url=item['link'],callback=self.parse_detail, meta={'item': item})# 新建一個解析方法 用于解析詳情頁 里面一定要有resposne參數
def parse_detail(self,response):# 獲取meta傳遞過來的參數給item字典接收item = resposne.meta["item"]
特別注意
- meta參數是一個字典
- meta字典中有一個固定的鍵
proxy
,表示代理ip,關于代理ip的使用我們將在scrapy的下載中間件進行介紹
scrapy中間件的使用
學習目標:
- 應用 scrapy中使用間件使用隨機UA的方法
- 應用 scrapy中使用代理ip的的方法
1.1 scrapy中間件的分類
根據scrapy運行流程中所在位置不同分為:
- 下載中間件
- 爬蟲中間件
scrapy默認情況下,兩中中間件都是在middlewares.py一個文件中,爬蟲中間件使用方法和下載中間件相同,且功能重復,通常使用下載中間件
1.2 scrapy中間件的作用:預處理request和response對象
- 對header以及cookie進行更換和處理
- 使用代理ip等
- 對請求進行定制化操作
2. 下載中間件的使用方法:
接下來我們對豆瓣爬蟲進行修改完善,通過下載中間件來學習如何使用中間件
編寫一個Downloader Middlewares和我們編寫一個pipeline一樣,定義一個類,然后在setting中開啟
Downloader Middlewares默認的方法:
-
process_request(self, request, spider):
- 當每個request通過下載中間件時,該方法被調用。
2. 返回None值:沒有return也是返回None,該request對象傳遞給下載器,或通過引擎傳遞給其他權重低的process_request方法
3. 返回Response對象:不再請求,把response返回給引擎
4. 返回Request對象:把request對象通過引擎交給調度器,此時將不通過其他權重低的process_request方法
- 當每個request通過下載中間件時,該方法被調用。
-
process_response(self, request, response, spider):
- 當下載器完成http請求,傳遞響應給引擎的時候調用
2. 返回Resposne:通過引擎交給爬蟲處理或交給權重更低的其他下載中間件的process_response方法
3. 返回Request對象:通過引擎交給調取器繼續請求,此時將不通過其他權重低的process_request方法
- 當下載器完成http請求,傳遞響應給引擎的時候調用
-
在settings.py中配置開啟中間件,權重值越小越優先執行
3. 定義實現隨機User-Agent的下載中間件
3.1 在middlewares.py中完善代碼 middlewares.py中自帶的代碼可以刪除掉
import random
class UserAgentDownloadMiddleware(object):user_agent = ['Mozilla/5.0 (Windows NT 6.1; WOW64; rv:77.0) Gecko/20190101 Firefox/77.0','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36','Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16.2']# 方法名是scrapy規定的方法 (協商機制)# 每個交給下載器的request對象都會經過該方法,并期望返回responsedef process_request(self, request, spider):# 獲取隨機請求頭u_a = random.choice(self.user_agent)# 設置請求頭request.headers['User-Agent'] = u_a
3.2 在settings中設置開啟自定義的下載中間件,設置方法同管道
settings文件所寫參數的詳細說明可參考以下博客:
https://blog.csdn.net/Lan_cer/article/details/87554025
DOWNLOADER_MIDDLEWARES = {'douban.middlewares.UserAgentDownloadMiddleware': 200,
}
4. 代理ip的使用
4.1 思路分析
- 代理添加的位置:request.meta中增加
proxy
字段 - 獲取一個代理ip,賦值給
request.meta['proxy']
- 代理池中隨機選擇代理ip
- 像代理ip的api發送請求獲取一個代理ip
4.2 具體實現
class RandomProxy(object):ip_list = ['116.26.39.23:4215','42.56.239.136:4278','115.234.192.226:4275',]def process_request(self, request, spider):proxy = random.choice(self.ip_list)# 需要加上https://,否則報錯# 修改請求的元數據字典 用于給框架中其他組件傳遞信息 比如給其添加一個代理request.meta['proxy'] = 'https://' + proxy
同理要在settings.py中開啟該中間件
DOWNLOADER_MIDDLEWARES = {'douban.middlewares.RandomProxy': 100,'douban.middlewares.UserAgentDownloadMiddleware': 200,
}
scrapy管道的使用
學習目標:
1. 掌握 scrapy管道(pipelines.py)的使用
之前我們在scrapy入門使用一節中學習了管道的基本使用,接下來我們深入的學習scrapy管道的使用
process_item(self,item,spider):
- 管道類中必須有的函數
- item指引擎傳過來的數據 實現對item數據的處理
- 必須return item
- spide指的是使用這個管道的爬蟲
2. 管道文件的修改
繼續完善豆瓣爬蟲,在pipelines.py代碼中完善
# -*- coding:utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html# useful for handling different item types with a single interface
import json
import loggingfrom itemadapter import ItemAdapter
import pymysqlclass DoubanPipeline:def __init__(self):self.file = open('douban.json', 'a', encoding='utf-8')def process_item(self, item, spider):data = dict(item)json_data = json.dumps(data, ensure_ascii=False) + ',\n'self.file.write(json_data)# 不return的情況下,另一個權重較低的pipeline將不會獲得itemreturn item# 整個程序生命周期結束 內存銷毀 該方法才會執行結束def __del__(self):self.file.close()class DoubansqlPipeline:def __init__(self):# 連接數據庫 用戶名 密碼 數據庫名 編碼self.db = pymysql.connect(user='root', password='admin', database='xiaoxiao', charset='utf8')self.cursor = self.db.cursor() # 獲取操作游標def process_item(self, item, spider):# 此時item對象必須是一個字典,再插入,如果此時item是BaseItem則需要先轉換為字典:dict(BaseItem)item = dict(item)# print(item)try:sql = 'insert into db_data(name,content,link,txt) values(%s,%s,%s,%s)' # SQL語句self.cursor.execute(sql, [item['name'], item['content'], item['link'], item['txt']]) # 執行sql語句self.db.commit() # 提交except Exception as e:logging.error(f'數據存儲異常,原因:{e}')# 不return的情況下,另一個權重較低的pipeline將不會獲得itemreturn item# 當所屬類運行完成 這個方法就會關閉掉def close_spider(self, spider):self.db.close()
3. 開啟管道
在settings.py設置開啟pipeline:
ITEM_PIPELINES = {'douban.pipelines.DoubanPipeline': 300, # 300表示權重'douban.pipelines.DoubansqlPipeline': 301, # 權重值越小,越優先執行!
}
4. pipeline使用注意點
- 使用之前需要在settings中開啟
- pipeline在setting中鍵表示位置(即pipeline在項目中的位置可以自定義),值表示距離引擎的遠近,越近數據會越先經過:權重值小的優先執行
- 有多個pipeline的時候,process_item的方法必須return item,否則后一個pipeline取到的數據為None值
- pipeline中process_item的方法必須有,否則item沒有辦法接受和處理
- process_item方法接受item和spider,其中spider表示當前傳遞item過來的spider
- def init(self): (或者可以寫def open_spider(spider) 😃 都表示能夠在爬蟲開啟的時候執行一次
- def close_spider(self, spider): 能夠在爬蟲關閉的時候執行一次
- 上述倆個方法經常用于爬蟲和數據庫的交互,在爬蟲開啟的時候建立和數據庫的連接,在爬蟲關閉的時候斷開和數據庫的連接
小結
- 管道能夠實現數據的清洗和保存,能夠定義多個管道實現不同的功能,其中有個三個方法
- process_item(self,item,spider):實現對item數據的處理
- open_spider(self, spider): 在爬蟲開啟的時候僅執行一次
- close_spider(self, spider): 在爬蟲關閉的時候僅執行一次
scrapy.Request發送post請求
我們可以通過scrapy.Request()指定method、body參數來發送post請求;也可以使用scrapy.FormRequest()來發送post請求
1 發送post請求
注意:scrapy.FormRequest()能夠發送表單和ajax請求,參考閱讀 https://www.jb51.net/article/146769.htm
2 思路分析
-
找到post的url地址:然后定位url地址為https://www.bydauto.com.cn/api/comom/search_join_shop
-
找到請求體的規律:分析post請求的請求體(參數)
-
start_urls中的url地址是交給parse處理的,如有必要,我們需要重寫start_request這個定制方法:
爬蟲文件
import scrapy
import json
from jsonpath import jsonpathclass BydSpiderSpider(scrapy.Spider):name = 'byd_spider'# 1.檢查域名allowed_domains = ['bydauto.com']# 2.修改請求url# start_urls = ['https://www.bydauto.com.cn/api/comom/search_join_shop']# 注意post請求的起始url發請求的那一刻要求是str類型city_url = 'https://www.bydauto.com.cn/api/comom/search_join_shop'# post請求的參數payload = {"type": 2, "province": 430000, "city": 430100, "network": 'null'}# 3,構造起始方法:start_requests(self),此方法是spider模塊中的定制方法,是一個重寫方法,不能修改名字和參數# 作用:爬蟲從該方法開始,此時start_urls 和 parse( ) 函數可刪除,可在該start_requests函數中寫入多種請求def start_requests(self):# 4.將請求信息打包成一個請求對象 并將返回的響應數據交給parse方法處理yield scrapy.Request(url=self.city_url,method='POST',body=json.dumps(self.payload),callback=self.parse)# 5.解析比亞迪地址和電話信息def parse(self, response):json_data = response.json()address = jsonpath(json_data,'$..address')print(address)tel = jsonpath(json_data,'$..tel')print(tel)