這兩天摸索了下scrapy,剛看文檔的時(shí)候覺得有點(diǎn)生無可戀,scrapy框架個(gè)人還是覺得比較難懂的,需要學(xué)習(xí)的地方非常多,之前用beautifulsoup4爬過top250,比scrapy簡(jiǎn)單更容易理解??!
Scrapy簡(jiǎn)介
Scrapy,Python開發(fā)的一個(gè)快速、高層次的屏幕抓取和web抓取框架,用于抓取web站點(diǎn)并從頁(yè)面中提取結(jié)構(gòu)化的數(shù)據(jù)。Scrapy用途廣泛,可以用于數(shù)據(jù)挖掘、監(jiān)測(cè)和自動(dòng)化測(cè)試。 下面對(duì)每個(gè)組件都做了簡(jiǎn)單介紹,數(shù)據(jù)流如下所描述。

- Scrapy Engine
引擎負(fù)責(zé)控制數(shù)據(jù)流在系統(tǒng)中所有組件中流動(dòng),并在相應(yīng)動(dòng)作發(fā)生時(shí)觸發(fā)事件。 詳細(xì)內(nèi)容查看下面的數(shù)據(jù)流(Data Flow)部分 - 調(diào)度器(Scheduler)
調(diào)度器從引擎接受request并將他們?nèi)腙?duì),以便之后引擎請(qǐng)求他們時(shí)提供給引擎 - 下載器(Downloader)
下載器負(fù)責(zé)獲取頁(yè)面數(shù)據(jù)并提供給引擎,而后提供給spider - Spiders
Spider是Scrapy用戶編寫用于分析response并提取item(即獲取到的item)或額外跟進(jìn)的URL的類。 每 個(gè)spider負(fù)責(zé)處理一個(gè)特定(或一些)網(wǎng)站 - Item Pipeline
Item Pipeline負(fù)責(zé)處理被spider提取出來的item。典型的處理有清理、 驗(yàn)證及持久化(例如存取到數(shù) 據(jù)庫(kù)中) - 下載器中間件(Downloader middlewares)
下載器中間件是在引擎及下載器之間的特定鉤子(specific hook),處理Downloader傳遞給引擎的 response。 其提供了一個(gè)簡(jiǎn)便的機(jī)制,通過插入自定義代碼來擴(kuò)展Scrapy功能 - Spider中間件(Spider middlewares)
Spider中間件是在引擎及Spider之間的特定鉤子(specific hook),處理spider的輸入(response)和輸出 (items及requests)。 其提供了一個(gè)簡(jiǎn)便的機(jī)制,通過插入自定義代碼來擴(kuò)展Scrapy功能 - 數(shù)據(jù)流(Data flow)
Scrapy中的數(shù)據(jù)流由執(zhí)行引擎控制,其過程如下:
引擎打開一個(gè)網(wǎng)站(open a domain),找到處理該網(wǎng)站的Spider并向該spider請(qǐng)求第一個(gè)要爬取的 URL(s)。
引擎從Spider中獲取到第一個(gè)要爬取的URL并在調(diào)度器(Scheduler)以Request調(diào)度。
引擎向調(diào)度器請(qǐng)求下一個(gè)要爬取的URL。
調(diào)度器返回下一個(gè)要爬取的URL給引擎,引擎將URL通過下載中間件(請(qǐng)求(request)方向)轉(zhuǎn)發(fā)給下載 器(Downloader)。
一旦頁(yè)面下載完畢,下載器生成一個(gè)該頁(yè)面的Response,并將其通過下載中間件(返回(response)方 向)發(fā)送給引擎。
引擎從下載器中接收到Response并通過Spider中間件(輸入方向)發(fā)送給Spider處理。
Spider處理Response并返回爬取到的Item及(跟進(jìn)的)新的Request給引擎。
引擎將(Spider返回的)爬取到的Item給Item Pipeline,將(Spider返回的)Request給調(diào)度器。
(從第二步)重復(fù)直到調(diào)度器中沒有更多地request,引擎關(guān)閉該網(wǎng)站
scrapy的流程如圖,并且可歸納如下
- 首先下載器下載request回執(zhí)的html等的response
- 然后下載器傳給爬蟲解析
- 接著爬蟲解析后交給調(diào)度器過濾,查重等等
- 最后交給管道,進(jìn)行爬取數(shù)據(jù)的處理
建議大家參考下中文版的Scrapy文檔,看文檔還是比較枯燥的,Scrapy又比較難懂(大神忽略),務(wù)必要有耐心!?。。。?!
實(shí)戰(zhàn)應(yīng)用
首先下載Scrapy包
pip install scrapy
這樣安裝,windows平臺(tái)應(yīng)該是會(huì)報(bào)錯(cuò)的,我當(dāng)時(shí)裝Scrapy時(shí)廢了很大勁才弄完。安裝過程中Pycharm或者官網(wǎng)上找不到的模塊可以上這個(gè)網(wǎng)址找Unofficial Windows Binaries for Python Extension Packages
Pywin32
接著,我們打開CMD,新建一個(gè)爬蟲文件
scrapy startproject douban
C:\Users\ssaw\douban>tree /f
C:.
│ scrapy.cfg
│
└─douban
│ items.py
│ middlewares.py
│ pipelines.py
│ settings.py
│ init.py
│
└─spiders
init.py
簡(jiǎn)單介紹下這些文件
- scrapy.cfg: 項(xiàng)目的配置文件
- douban/: 該項(xiàng)目的python模塊。之后您將在此加入代碼
- douban/items.py: 項(xiàng)目中的item文件
- douban/pipelines.py: 項(xiàng)目中的pipelines文件
- douban/settings.py: 項(xiàng)目的設(shè)置文件
- douban/spiders/:放置spider代碼的目錄
編輯items.py文件
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class DoubanItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
movie_name = scrapy.Field()
movie_star = scrapy.Field()
movie_quote = scrapy.Field()
- 首先,引入Scrapy
- 接著,創(chuàng)建一個(gè)類,繼承自scrapy.item,這個(gè)是用來儲(chǔ)存要爬下來的數(shù)據(jù)的存放容器,類似orm的寫法
- 我們要記錄的是:電影的名字、電影的評(píng)分、電影的引述(quote)
現(xiàn)在,我們?cè)趕piders文件夾下創(chuàng)建douban_spider.py
在我們編寫爬蟲之前,先了解一下scrapy的爬取機(jī)制,scrapy提取數(shù)據(jù)有自己的一套機(jī)制。它們被稱作選擇器(seletors),因?yàn)樗麄兺ㄟ^特定的 XPath 或者 CSS 表達(dá)式來“選擇” HTML文件中的某個(gè)部分。
之前我在博客上也總結(jié)過一篇使用Xpath模擬登陸GitHub有興趣可以看一下
附上Xpath學(xué)習(xí)教程Xpath
獲取網(wǎng)頁(yè)數(shù)據(jù)
打開Chrome(F12),查找元素
豆瓣電影TOP250
紅框圈出來的分別代表
movie_name = div[@class="hd"]/a/span/
movie_star = div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]
movie_quote = div[@class="bd"]/p[@class="quote"]/span[@class="inq"]
# _*_ coding=utf-8 _*_
from scrapy.spiders import Spider
from scrapy.selector import Selector
class DouBanSpider(Spider):
name = 'db'
start_urls = ['https://movie.douban.com/top250']
def parse(self, response):
# print(response.body)
selector = Selector(response)
# print(selector)
movies = selector.xpath('//div[@class="info"]')
for movie in movies:
movie_name = movie.xpath('div[@class="hd"]/a/span/text()').extract()
movie_star = movie.xpath('div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()').extract()
movie_quote = movie.xpath('div[@class="bd"]/p[@class="quote"]/span[@class="inq"]/text()').extract()
print(movie_name)
print(movie_star)
print(movie_quote)
我們打開setting.py,加上U-A
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
在CMD中輸入scrapy crawl db,運(yùn)行如下:
我們還可以在douban文件根目錄創(chuàng)建一個(gè)main.py,這樣就可以在Pycharm中運(yùn)行了
from scrapy import cmdline
cmdline.execute("scrapy crawl db".split())
使用Item
Item對(duì)象是自定義的python字典。 您可以使用標(biāo)準(zhǔn)的字典語(yǔ)法來獲取到其每個(gè)字段的值。(字段即是我們之前用Field賦值的屬性),Spider將會(huì)將爬取到的數(shù)據(jù)以 Item 對(duì)象返回。
item = DoubanItem()
item['movie_name'] = movie_name
item['movie_star'] = movie_star
item['movie_quote'] = movie_quote
好了,現(xiàn)在我們先試著爬取單頁(yè)面,查看下結(jié)果后,再去爬取多頁(yè)面
from scrapy.spiders import Spider
from scrapy.selector import Selector
from douban.items import DoubanItem
class DouBanSpider(Spider):
name = 'db'
start_urls = ['https://movie.douban.com/top250']
def parse(self, response):
# print(response.body)
selector = Selector(response)
# print(selector)
movies = selector.xpath('//div[@class="info"]')
for movie in movies:
movie_name = movie.xpath('div[@class="hd"]/a/span/text()').extract()
movie_star = movie.xpath('div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()').extract()
movie_quote = movie.xpath('div[@class="bd"]/p[@class="quote"]/span[@class="inq"]/text()').extract()
#print(movie_name)
#print(movie_star)
#print(movie_quote)
item = DoubanItem()
item['movie_name'] = movie_name
item['movie_star'] = movie_star
item['movie_quote'] = movie_quote
yield item
print(movie_name)
print(movie_star)
print(movie_quote)
- 首先,我們先從scrapy中獲得所需要的通用的 spider和 selector
- 接著,我們使用Item將爬取的數(shù)據(jù)返回
- 創(chuàng)建了一個(gè)DouBanSpider,繼承了 scrapy.Spider 類, 且定義以下三個(gè)屬性:
- name: 用于區(qū)別Spider。 該名字必須是唯一的,您不可以為不同的Spider設(shè)定相同的名字
- start_urls: 包含了Spider在啟動(dòng)時(shí)進(jìn)行爬取的url列表。 因此,第一個(gè)被獲取到的頁(yè)面將是其中之一, 后續(xù)的URL則從初始的URL獲取到的數(shù)據(jù)中提取。
- parse() 是spider的一個(gè)方法。 被調(diào)用時(shí),每個(gè)初始URL完成下載后生成的 Response 對(duì)象將會(huì)作為唯一的參數(shù)傳遞給該函數(shù)。 該方法負(fù)責(zé)解析返回的數(shù)據(jù)(response data),提取數(shù)據(jù)(生成item)以及生成需要進(jìn)一步處理的URL的 Request 對(duì)象
- seletor的方法返回后一定要用它的 extract()方法,來返回一個(gè)列表 ;extract()文檔的定義:串行化并將匹配到的節(jié)點(diǎn)返回一個(gè)unicode字符串列表。 結(jié)尾是編碼內(nèi)容的百分比
- 接著,我們把得到的數(shù)據(jù)保存在Item中
- 最后,我們使用Feed exports來保存數(shù)據(jù)
- 這里使用了yield生成器函數(shù),生成器函數(shù)和迭代器有密切關(guān)系,也并不是很容易理解,這一點(diǎn)需要多多學(xué)習(xí)
#看一個(gè)yield函數(shù)的示例
#函數(shù)在每次循環(huán)時(shí)都會(huì)產(chǎn)生一個(gè)值,之后將其返回給它的調(diào)用者
#函數(shù)不斷的生成數(shù)字的平方
def gensquares(N):
for i in range(N):
yield i ** 2
for i in gensquares(5):
print(i, end=' ')
0 1 4 9 16
好了,spider暫時(shí)寫完了,我們?cè)囍\(yùn)行下
scrapy crawl db -o douban.json -t json
-o 后面是導(dǎo)出文件名,-t 后面是導(dǎo)出類型。
然后來看一下導(dǎo)出的結(jié)果,Pycharm打開json文件即可
打印出來的內(nèi)容需要編碼
我們換種方式,把文件格式保存為CSV,使用EXCEL打開
scrapy crawl db -o douban.csv -t csv
用excel打開后假如是一堆亂碼,就使用記事本打開,把它“另存為”時(shí),編碼選擇ANSI
好了,現(xiàn)在我們添加多頁(yè)面鏈接,完整地把TOP250爬取下來
我們從Elements中找到翻頁(yè)lianjie
next_page =response.selector.xpath('//span[@class="next"]/link/@href').extract()
if next_page:
next_page = next_page[0]
print(next_page)
yield Request(self.url + next_page, callback=self.parse)
CMD輸入scrapy crawl db -o douban.csv -t csv ,開始運(yùn)行
學(xué)習(xí)Scrapy需要反復(fù)查看文檔、資料,這是個(gè)簡(jiǎn)單的學(xué)習(xí)總結(jié),這兩天準(zhǔn)備再去學(xué)習(xí)MongoDB數(shù)據(jù)庫(kù),大家一起加油??!