1、什么是scrapy
Scrapy是一個為了爬取網站數據,提取結構性數據而編寫的應用框架,我們只需要實現少量的代碼,就能夠快速的抓取。Scrapy 使用了Twisted['twɪstɪd]異步
網絡框架
文檔地址:https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/commands.html
2、scrapy環境配置
pip install Scrapy
3、scrapy的流程
其流程可以描述如下:
- 調度器把requests-->引擎-->下載中間件--->下載器
- 下載器發送請求,獲取響應---->下載中間件---->引擎--->爬蟲中間件--->爬蟲
- 爬蟲提取url地址,組裝成request對象---->爬蟲中間件--->引擎--->調度器
- 爬蟲提取數據--->引擎--->管道
- 管道進行數據的處理和保存
注意:
- 圖中綠色線條的表示數據的傳遞
- 注意圖中中間件的位置,決定了其作用
- 注意其中引擎的位置,所有的模塊之前相互獨立,只和引擎進行交互
scrapy中每個模塊的具體作用
4、創建scrapy項目
# 命令: scrapy startproject +<項目名字> #示例: scrapy startproject myspider
生成的目錄和文件結果如下:
5、 創建爬蟲
首先進入創建的項目目錄里面的spiders目錄,然后執行下面的命令創建爬蟲
# 命令: scrapy genspider +<爬蟲名字> + <允許爬取的域名> # 示例: scrapy genspider itcast itcast.cn
6、完善spider, 將數據傳遞到pipeline
完善spider即通過方法進行數據的提取等操作
# -*- coding: utf-8 -*- import scrapy class ItcastSpider(scrapy.Spider): name = 'itcast' # 爬蟲名 [爬蟲啟動時,使用scrapy crawl itcast] allowed_domains = ['itcast.cn'] # 允許爬取的范圍,防止爬蟲爬到其他網站 start_urls = ['http://www.itcast.cn/channel/teacher.shtml'] # 爬蟲最開始抓取的url地址 def parse(self, response): # 數據提取方法,處理start_url地址中的響應,接受下載中間件傳過來的response響應 # 先分組,再進行數據的提取 li_list = response.xpath('//div[@class="tea_con"]/div/ul/li') for li in li_list: item = {} item['name'] = li.xpath('.//h3/text()').extract_first() item['title'] = li.xpath('.//h4/text()').extract_first() item['desc'] = li.xpath('.//p/text()').extract_first() print(item) yield item # 將數據傳給pipeline
注意:
response.xpath
方法的返回結果是一個類似list的類型,其中包含的是selector對象,操作和列表一樣,但是有一些額外的方法extract()
返回一個包含有字符串的列表extract_first()
返回列表中的第一個字符串,列表為空沒有返回None- spider中的parse方法必須有
- 需要抓取的url地址必須屬於allowed_domains,但是start_urls中的url地址沒有這個限制
- 啟動爬蟲的時候注意啟動的位置,是在項目路徑下啟動
為什么要使用yield?
- 讓整個函數變成一個生成器,有什么好處呢?
- 遍歷這個函數的返回值的時候,挨個把數據讀到內存,不會造成內存的瞬間占用過高
- python3中的range和python2中的xrange同理
注意:
- yield能夠傳遞的對象只能是:
BaseItem
,Request
,dict
,None
7、完善pipeline
pipeline在settings中能夠開啟多個,為什么需要開啟多個?
- 不同的pipeline可以處理不同爬蟲的數據
- 不同的pipeline能夠進行不同的數據處理的操作,比如一個進行數據清洗,一個進行數據的保存
pipeline使用注意點
- 使用之前需要在settings中開啟
- pipeline在setting中鍵表示位置(即pipeline在項目中的位置可以自定義),值表示距離引擎的遠近,越近數據會越先經過
- 有多個pipeline的時候,process_item的方法必須
return item
,否則后一個pipeline取到的數據為None值 - pipeline中process_item的方法必須有,否則item沒有辦法接受和處理
- process_item方法接受item和spider,其中spider表示當前傳遞item過來的spider
8、 scrapy啟動爬蟲項目
# 進去爬蟲項目目錄,執行以下命令 scrapy crawl "爬蟲名"
8、 scrapy實現翻頁請求
對於要提取如下圖中所有頁面上的數據該怎么辦?
回顧requests模塊是如何實現翻頁請求的:
- 找到下一頁的URL地址
- 調用requests.get(url)
思路:
- 找到下一頁的url地址
- 構造url地址的請求,傳遞給引擎
8.1 實現翻頁請求
-
使用方法
在獲取到url地址之后,可以通過
scrapy.Request(url,callback)
得到一個request對象,通過yield關鍵字就可以把這個request對象交給引擎 - 具體使用
# scrapy.Request的參數介紹 scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False]) # 括號中的參數為可選參數 # callback:表示當前的url的響應交給哪個函數去處理 # meta:實現數據在不同的解析函數中傳遞,meta默認帶有部分數據,比如# 下載延遲,請求深度等 # dont_filter:默認會過濾請求的url地址,即請求過的url地址不會繼續被請求,對需要重復請求的url地址可以把它設置為Ture,比如貼吧的翻頁請求,頁面的數據總是在變化;start_urls中的地址會被反復請求,否則程序不會啟動
# -*- coding: utf-8 -*- import re from copy import deepcopy import scrapy class SnSpider(scrapy.Spider): name = 'sn' allowed_domains = ['suning.com'] start_urls = ['https://book.suning.com/'] def parse(self, response): # 解析數據 pass # 獲取下一頁url地址 next_part_url = "" yield scrapy.Request(next_part_url, callback=self.parse_book_list, meta={"item": deepcopy(item)}) # scrapy.Request()能構建一個request對象,同時指定提取數據的callback函數 def parse_next_list(self, response): # 處理下一頁的數據提取
3. 添加User-Agent
同時可以再在setting中設置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'
9、 定義Item
-
定義Item的原因
定義item即提前規划好哪些字段需要抓取,scrapy.Field()僅僅是提前占坑,通過item.py能夠讓別人清楚自己的爬蟲是在抓取什么,同時定義好哪些字段是需要抓取的,沒有定義的字段不能使用,防止手2
-
定義Item
3. 使用Item
Item使用之前需要先導入並且實例化,之后的使用方法和使用字典相同
from yangguang.items import YangguangItem item = YangguangItem() #實例化
10、 輸出日志LOG的設置
為了讓我們自己希望輸出到終端的內容能容易看一些,我們可以在setting中設置log級別
在setting中添加一行(全部大寫):LOG_LEVEL = "WARNING”
默認終端顯示的是debug級別的log信息
scrapy的debug信息
每次程序啟動后,默認情況下,終端都會出現很多的debug信息,那么下面我們來簡單認識下這些信息
11、認識scrapy中的setting文件
-
為什么項目中需要配置文件
- 在配置文件中存放一些公共變量,在后續的項目中便便修改,注意其中的變量名一般全部大寫
-
配置文件中的變量使用方法
- 導入即可使用
-
settings.py
中的重點字段和內涵
# USER_AGENT 設置ua # ROBOTSTXT_OBEY 是否遵守robots協議,默認是遵守 # CONCURRENT_REQUESTS 設置並發請求的數量,默認是16個 # DOWNLOAD_DELAY 下載延遲,默認無延遲 # COOKIES_ENABLED 是否開啟cookie,即每次請求帶上前一次的cookie,默認是開啟的 # DEFAULT_REQUEST_HEADERS 設置默認請求頭 # SPIDER_MIDDLEWARES 爬蟲中間件,設置過程和管道相同 # DOWNLOADER_MIDDLEWARES 下載中間件
12、管道中的open_spider
和close_spider
的方法
在管道中,除了必須定義process_item之外,還可以定義兩個方法:
open_spider(spider)
:能夠在爬蟲開啟的時候執行一次close_spider(spider)
:能夠在爬蟲關閉的時候執行一次
所以,上述方法經常用於爬蟲和數據庫的交互,在爬蟲開啟的時候建立和數據庫的連接,在爬蟲關閉的時候斷開和數據庫的連接
下面的代碼分別以操作文件和mongodb為例展示方法的使用:
13、crawlspider類的使用
1. crawlspider是什么
回顧之前的代碼中,我們有很大一部分時間在尋找下一頁的url地址或者是內容的url地址上面,這個過程能更簡單一些么?
思路:
- 從response中提取所有的滿足規則的url地址
- 自動的構造自己requests請求,發送給引擎
對應的crawlspider就可以實現上述需求,匹配滿足條件的url地址,才發送給引擎,同時能夠指定callback函數
2. 認識crawlspider爬蟲
2.1 創建crawlspdier爬蟲的命令
scrapy genspider –t crawl itcast itcast.cn
2.2 觀察爬蟲內的默認內容
spider中默認生成的內容如下,其中重點在rules中
- rules是一個元組或者是列表,包含的是Rule對象
- Rule表示規則,其中包含
LinkExtractor
,callback
和follow
LinkExtractor
:連接提取器,可以通過正則或者是xpath來進行url地址的匹配callback
:表示經過連接提取器提取出來的url地址響應的回調函數,可以沒有,沒有表示響應不會進行回調函數的處理follow
:表示進過連接提取器提取的url地址對應的響應是否還會繼續被rules中的規則進行提取,True表示會,Flase表示不會
class Itcast1Spider(CrawlSpider): name = 'itcast1' allowed_domains = ['itcast.cn'] start_urls = ['http://itcast.cn/'] rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), ) def parse_item(self, response): i = {} #使用xpath進行數據的提取或者url地址的提取 return i
2.3 crawlspider的使用
指定兩個正則規則,匹配url
rules = ( Rule(LinkExtractor(allow=r'position_detail.php\?id=\d+&keywords=&tid=0&lid=0'), callback='parse_item'), Rule(LinkExtractor(allow=r'position.php\?&start=\d+#a'), follow=True), )
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class TcSpider(CrawlSpider): name = 'tc' allowed_domains = ['tencent.com'] start_urls = ['https://hr.tencent.com/position.php'] rules = ( Rule(LinkExtractor(allow=r'position_detail.php\?id=\d+&keywords=&tid=0&lid=0'), callback='parse_item'), Rule(LinkExtractor(allow=r'position.php\?&start=\d+#a'), follow=True), ) def parse_item(self, response): item = {} # i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract() # i['name'] = response.xpath('//div[@id="name"]').extract() # i['description'] = response.xpath('//div[@id="description"]').extract() item["title"] = response.xpath("//td[@id='sharetitle']/text()").extract_first() # 提取標題 item['duty'] = response.xpath("//div[text()='工作職責:']/following-sibling::ul[1]/li/text()").extract() item['require'] = response.xpath("//div[text()='工作要求:']/following-sibling::ul[1]/li/text()").extract() print(item)
2.4 crawlspider使用的注意點
2.5 crawlspider的補充知識點(了解)
14、下載中間件和模擬登陸
14.1 scrapy中下載中間件的使用
-
使用方法:
編寫一個
Downloader Middlewares
和我們編寫一個pipeline一樣,定義一個類,然后在setting中開啟 -
Downloader Middlewares
默認的方法:-
process_request(self, request, spider):
-
當每個request通過下載中間件時,該方法被調用。
返回None值:繼續請求
返回Response對象:不在請求,把response返回給引擎
返回Request對象:把request對象交給調度器進行后續的請求
-
- process_response(self, request, response, spider):
- 當下載器完成http請求,傳遞響應給引擎的時候調用 - 返回Resposne:交給process_response來處理 - 返回Request對象:交給調取器繼續請求
3. 定義實現隨機User-Agent的下載中間件
class UserAgentMiddleware(object): def process_request(self,request,spider): agent = random.choice(agents) request.headers['User-Agent'] = agent
4. 定義實現隨機使用代理的下載中間件
class ProxyMiddleware(object): def process_request(self,request,spider): proxy = random.choice(proxies) request.meta['proxy'] = proxy
14.2. 使用scrapy進行模擬登陸
2.1 回顧之前的模擬登陸的方法
-
requests是如何模擬登陸的?
- 直接攜帶cookies請求頁面
- 找接口發送post請求存儲cookie
-
selenium是如何模擬登陸的?
- 找到對應的input標簽,輸入文字點擊登錄
scrapy來說,有兩個方法模擬登陸:
1、直接攜帶cookie
2、找到發送post請求的url地址,帶上信息,發送請求
2.2 scrapy攜帶cookie進行模擬登陸
-
攜帶cookie進行模擬登陸應用場景:
- cookie過期時間很長,常見於一些不規范的網站
- 能在cookie過期之前把搜有的數據拿到
- 配合其他程序使用,比如其使用selenium把登陸之后的cookie獲取到保存到本地,scrapy發送請求之前先讀取本地cookie
-
scrapy的start_requests方法的學習
scrapy中start_url是通過start_requests來進行處理的,其實現代碼如下
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_url地址中的url是需要登錄后才能訪問的url地址,則需要重寫start_request
方法並在其中手動添加上cookie
3. 實現攜帶cookie登錄人人網,例如:
注意:scrapy中cookie不能夠放在headers中,在構造請求的時候有專門的cookies參數,能夠接受字典形式的coookie
import scrapy import re class RenrenSpider(scrapy.Spider): name = 'renren' allowed_domains = ['renren.com'] start_urls = ['http://www.renren.com/941954027/profile'] def start_requests(self): cookie_str = "cookie_str" cookie_dict = {i.split("=")[0]:i.split("=")[1] for i in cookie_str.split("; ")} yield scrapy.Request( self.start_urls[0], callback=self.parse, cookies=cookie_dict, # headers={"Cookie":cookie_str} ) def parse(self, response): ret = re.findall("新用戶287",response.text) print(ret) yield scrapy.Request( "http://www.renren.com/941954027/profile?v=info_timeline", callback=self.parse_detail ) def parse_detail(self,response): ret = re.findall("新用戶287",response.text) print(ret)
4. 在settings中開啟cookie_debug
在settings.py中通過設置COOKIES_DEBUG=TRUE
能夠在終端看到cookie的傳遞傳遞過程
2.3 scrapy發送post請求
scrapy中發送post請求的方法 通過scrapy.FormRequest
能夠發送post請求,同時需要添加fromdata
參數作為請求體,以及callback
使用scrapy模擬登陸github
思路分析
-
找到post的url地址
點擊登錄按鈕進行抓包,然后定位url地址為
https://github.com/session
-
找到請求體的規律
分析post請求的請求體,其中包含的參數均在前一次的響應中
-
驗證是否登錄成功
通過請求個人主頁,觀察是否包含用戶名
#spider/github.py # -*- coding: utf-8 -*- import scrapy import re class GithubSpider(scrapy.Spider): name = 'github' allowed_domains = ['github.com'] start_urls = ['https://github.com/login'] def parse(self, response): authenticity_token = response.xpath("//input[@name='authenticity_token']/@value").extract_first() utf8 = response.xpath("//input[@name='utf8']/@value").extract_first() commit = response.xpath("//input[@name='commit']/@value").extract_first() yield scrapy.FormRequest( "https://github.com/session", formdata={ "authenticity_token":authenticity_token, "utf8":utf8, "commit":commit, "login":"noobpythoner", "password":"***" }, callback=self.parse_login ) def parse_login(self,response): ret = re.findall("noobpythoner",response.text,re.I) print(ret)