@TOC
Scrapy簡介
較為流行的python爬蟲框架喘先。
本文著重將記錄本人入門Scrapy時的所有精煉總結(jié)(除了一些書、官方文檔凫碌,同時也會借鑒一些比較好的blog的內(nèi)容死陆,因?yàn)闀鴮懙奶鷿俜轿臋n又搞得和過家家一樣其屏,亂的不行喇勋,根本沒法看)。希望能給大家?guī)韼椭诵校瑨伌u引玉川背。
如果爬下來的數(shù)據(jù)還不會分析,建議先看本人上一篇博文《BeautifulSoup總結(jié)及contents內(nèi)容分析》
Scrapy架構(gòu)
架構(gòu)如下圖所示:
圖中綠色線條代表了數(shù)據(jù)流向蛤袒。其他幾個則是其組件熄云。
//以下是我個人對這些組件的理解,并非官方文檔解釋
- 引擎(Scrapy Engine):負(fù)責(zé)整個數(shù)據(jù)流走向
- 調(diào)度器(scheduler):負(fù)責(zé)將Request入隊(duì)妙真,并在需要時提供給引擎
- 下載器(DownLoader): 負(fù)責(zé)提交Request缴允,并獲得對應(yīng)網(wǎng)站的Response,將其提交給spider下一步處理珍德。(可以根據(jù)用戶定義的<kbd>下載中間件</kbd>中的配置進(jìn)行自定義下載)
- 蜘蛛(Spider):負(fù)責(zé)處理網(wǎng)站返回的Response练般,提取Item 或者是 需要繼續(xù)跟進(jìn)的URL
- 數(shù)據(jù)管道(Item pipeline): 去重、過濾锈候、加工和存儲Item
- 下載中間件(Downloader middlewares):自定義擴(kuò)展下載功能的組件
- Spider 中間件 (Spider middlewares):自定義擴(kuò)展Engine和Spider中間通信的功能組件
Scrapy運(yùn)作流程
1 引擎:Hi薄料!Spider, 你要處理哪一個網(wǎng)站?
2 Spider:老大要我處理xxxx.com泵琳。
3 引擎:你把第一個需要處理的URL給我吧摄职。
4 Spider:給你,第一個URL是xxxxxxx.com获列。
5 引擎:Hi谷市!調(diào)度器,我這有request請求你幫我排序入隊(duì)一下击孩。
6 調(diào)度器:好的迫悠,正在處理你等一下。
7 引擎:Hi溯壶!調(diào)度器及皂,把你處理好的request請求給我甫男。
8 調(diào)度器:給你,這是我處理好的request
9 引擎:Hi验烧!下載器板驳,你按照老大的下載中間件的設(shè)置幫我下載一下這個request請求
10 下載器:好的!給你碍拆,這是下載好的東西若治。(如果失敗:sorry感混,這個request下載失敗了端幼。然后引擎告訴調(diào)度器,這個request下載失敗了弧满,你記錄一下婆跑,我們待會兒再下載)
11 引擎:Hi!Spider庭呜,這是下載好的東西滑进,并且已經(jīng)按照老大的下載中間件處理過了,你自己處理一下(注意募谎!這兒responses默認(rèn)是交給def parse()這個函數(shù)處理的)
12 Spider:(處理完畢數(shù)據(jù)之后對于需要跟進(jìn)的URL)扶关,Hi!引擎数冬,我這里有兩個結(jié)果节槐,這個是我需要跟進(jìn)的URL,還有這個是我獲取到的Item數(shù)據(jù)拐纱。
13 引擎:Hi 铜异!管道 我這兒有個item你幫我處理一下!調(diào)度器秸架!這是需要跟進(jìn)URL你幫我處理下熙掺。然后從第四步開始循環(huán),直到獲取完老大需要全部信息咕宿。
14 管道``調(diào)度器:好的,現(xiàn)在就做蜡秽!
參考: https://segmentfault.com/a/1190000013178839
項(xiàng)目文件目錄結(jié)構(gòu)
在命令行中府阀,執(zhí)行 scrapy startproject <項(xiàng)目名稱>即會在所在當(dāng)前目錄下創(chuàng)建項(xiàng)目目錄以及相關(guān)文件。
項(xiàng)目名
|—— 項(xiàng)目名
| |—— _init_.py #包定義
| |—— items.py #模型定義
| |—— middlewares.py #中間件定義
| |—— pipelines.py #管道定義
| |—— settings.py #配置文件芽突。編程方式控制的配置文件
| |—— spider
| |—— _init_.py #默認(rèn)蜘蛛代碼文件
|——— scrapy.cfg #運(yùn)行配置文件试浙。該文件存放的目錄為根目錄。模塊名的字段定義了項(xiàng)目的設(shè)置
最基本的Scrapy爬蟲制作流程
1寞蚌、新建項(xiàng)目
2田巴、明確目標(biāo):主要是編寫Item.py
3钠糊、制作爬蟲:主要是編寫spider.py
4、存儲內(nèi)容:主要是編寫pipelines.py
實(shí)戰(zhàn)
環(huán)境安裝
//建議直接安裝壹哺,不要用conda創(chuàng)建一個環(huán)境再安裝抄伍。
//因?yàn)閟crapy命令需要在全局使用,這樣才能在任何文件夾輕松調(diào)用管宵。
pip install Scrapy
//驗(yàn)證是否安裝成功截珍。運(yùn)行該命令出現(xiàn)圖中內(nèi)容即為成功。
scrapy
1箩朴、新建項(xiàng)目
項(xiàng)目介紹:從中新網(wǎng)爬取新聞供稿的標(biāo)題岗喉、鏈接、內(nèi)容和日期炸庞,并以json形式保存到本地钱床。
// 需要先cd到你想要存放該項(xiàng)目的路徑下
scrapy startproject chinanews_crawler
2、明確目標(biāo)
先查看以下目標(biāo)網(wǎng)站內(nèi)容:
中新網(wǎng):http://www.chinanews.com/rss/
然后用chrome的開發(fā)者工具埠居,查看以下我們需要的鏈接的位置查牌。
我們發(fā)現(xiàn),那些鏈接是被放在一個
<iframe>
元素內(nèi)拐格,也就是當(dāng)前展示的其實(shí)是兩個頁面組成的僧免,直接爬取該網(wǎng)站是拿不到鏈接的。于是捏浊,查看<ifreame>
元素懂衩,發(fā)現(xiàn)src="http://www.chinanews.com/rss/rss_2.html"
。于是金踪,打開該網(wǎng)頁浊洞,找到真正的鏈接所在。我們繼續(xù)F12查看其元素胡岔。
發(fā)現(xiàn)所有的新聞頻道鏈接都在一個
<a>
標(biāo)簽中法希。并且打開這些頻道后,就是即時新聞的內(nèi)容了,也就是到達(dá)了我們的目標(biāo)且轨。如下圖:然后叙赚,我們要的是title,link,description,pubDate。
最后屋剑,我們可以開始編寫代碼了。
文件:items.py
from scrapy.item import Item, Field
class ChinaNewsItem(Item):
title = Field() # 標(biāo)題
link = Field() # 詳情鏈接
desc = Field() # 新聞綜述
pub_date = Field() # 發(fā)布日期
3诗眨、制作爬蟲
可以直接在spider/文件夾下唉匾,編輯那個 init 文件,或者新建一個py文件匠楚,再者在命令行中巍膘,<kbd>scrapy genspider 爬蟲名 目標(biāo)網(wǎng)站厂财。</kbd>
無論那種方式,其實(shí)都是生成一個爬蟲的類峡懈。
執(zhí)行:scrapy genspider newsCrawler chinanews.com
打開新生成的spider/newsCrawler.py文件璃饱,內(nèi)容如下:
# -*- coding: utf-8 -*-
from scrapy.spider import Spider
class EasyNewsCrawlerItem(Spider):
name = 'newsCrawler' # 爬蟲名,啟動時需要用到逮诲,scrapy crawl newsCrawler帜平,就是啟動該爬蟲(對照運(yùn)行流程中第一步中,引擎:Hi,spider,你要處理哪個網(wǎng)站梅鹦。
allowed_domains = ['chinanews.com'] # 允許爬蟲搜索的域名范圍
start_urls = ['http://chinanews.com/'] # 起始爬取位置
def parse(self, response):
"""解析函數(shù)裆甩。response就是下載器下載好的頁面內(nèi)容,爬蟲就在該網(wǎng)站中齐唆,提取所需的Item"""
pass
根據(jù)<kbd>2嗤栓、明確目標(biāo)</kbd>中,Item的內(nèi)容箍邮,我們發(fā)現(xiàn)茉帅,直接爬取并不能實(shí)現(xiàn)。因?yàn)椋?a target="_blank" rel="nofollow">http://www.chinanews.com/rss/rss_2.html 這個頁面只有鏈接锭弊,要打開這些鏈接堪澎,才能看到正真的內(nèi)容。于是味滞,我們需要再寫一個解析的函數(shù)樱蛤,處理真正網(wǎng)站的內(nèi)容。
def parse_feed(self, response):
"""二次解析剑鞍,本次目標(biāo):解析出最后結(jié)果昨凡,包裝成Item"""
rss_page = BeautifulSoup(response.body, "lxml")
items = rss_page.find_all('item')
for item in items:
newsItem = EasyNewsCrawlerItem()
newsItem['title'] = item.title.text
# link是個自閉和標(biāo)簽,不能用item.link.text蚁署,原因可以看我上一篇博文
newsItem['link'] = item.contents[2]
newsItem['description'] = item.title.text
newsItem['pubDate'] = item.pubdate.text
yield newsItem
最后便脊,我們稍微處理一下第一個解析函數(shù),將二次解析定位轉(zhuǎn)一下就行了光戈。下面給出spider的完整代碼哪痰。
# -*- coding: utf-8 -*-
from scrapy.spider import Spider
from scrapy.http import Request
from bs4 import BeautifulSoup
from ..items import EasyNewsCrawlerItem
class NewscrawlerSpider(Spider):
name = 'newsCrawler' # 爬蟲名,啟動時需要用到久妆,scrapy crawl newsCrawler妒御,就是啟動該爬蟲(對照運(yùn)行流程中第一步中,引擎:Hi,spider,你要處理哪個網(wǎng)站镇饺。
allowed_domains = ['chinanews.com'] # 允許爬蟲搜索的域名范圍
start_urls = ['http://www.chinanews.com/rss/rss_2.html'] # 起始爬取位置
def parse(self, response):
"""解析函數(shù)。response就是下載器下載好的頁面內(nèi)容送讲,爬蟲就在該網(wǎng)站中奸笤,提取所需的Item"""
"""本次目標(biāo):解析出href中的鏈接惋啃,然后留給下一個解析函數(shù)繼續(xù)解析"""
# 不熟悉BeautifulSoup的可以看我上一個博文
rss_page = BeautifulSoup(response.body, "lxml")
"""拿到該網(wǎng)站后,先找到所有<a>標(biāo)簽监右,然后把其中的href的內(nèi)容保存起來边灭。"""
rss_links = set([item['href'] for item in rss_page.find_all("a")]) # 用set是為了濾掉重復(fù)鏈接
for link in rss_links:
yield Request(url=link, callback=self.parse_feed)
def parse_feed(self, response):
"""二次解析,本次目標(biāo):解析出最后結(jié)果健盒,包裝成Item"""
rss_page = BeautifulSoup(response.body, "lxml")
items = rss_page.find_all('item')
for item in items:
newsItem = EasyNewsCrawlerItem()
newsItem['title'] = item.title.text
# link是個自閉和標(biāo)簽绒瘦,不能用item.link.text,原因可以看我上一篇博文
newsItem['link'] = item.contents[2]
newsItem['description'] = item.title.text
newsItem['pubDate'] = item.pubdate.text
yield newsItem
"""如果是item.pubDate.text會失敗并報錯扣癣,然后我查了一下文檔惰帽,發(fā)現(xiàn):"""
"""
如果同樣的代碼在不同環(huán)境下結(jié)果不同,可能是因?yàn)閮蓚€環(huán)境下使用不同的解析器造成的.
例如這個環(huán)境中安裝了lxml,而另一個環(huán)境中只有html5lib, 解析器之間的區(qū)別中說明了原因.
修復(fù)方法是在 BeautifulSoup 的構(gòu)造方法中中指定解析器
因?yàn)镠TML標(biāo)簽是 大小寫敏感 的,所以3種解析器再出來文檔時都將tag和屬性轉(zhuǎn)換成小寫.
"""
"""結(jié)論:beautifulSoup會將tag統(tǒng)一變成小寫"""
4、 存儲內(nèi)容
存儲數(shù)據(jù)的方法有以下幾種:
- 通過pipeline(管道)存儲
- 全局性指定父虑。setting.py文件中配置存儲選項(xiàng)
- 動態(tài)指定该酗。命令行啟動時添加-o參數(shù)
<kbd>方法1:管道存儲</kbd>
為了加深對管道的理解,體現(xiàn)其功能士嚎,這里寫了三個管道呜魄,分別對應(yīng)過濾功能,加工功能莱衩,存儲功能爵嗅。每個管道都是一個擁有process_item方法的類。
同時笨蚁,管道寫好了后睹晒,要在setting.py文件中將管道配置一下,主要是控制 Item經(jīng)過管道的順序赚窃,可以取值為0-1000册招,值越小優(yōu)先級越高。
文件:pipelines.py
# -*- coding: utf-8 -*-
from scrapy.exceptions import DropItem
import time
from bs4 import BeautifulSoup
import json
class PreservationPipeline(object):
"""過濾性管道勒极。只通過最近一個小時以內(nèi)的新聞"""
def process_item(self, item, spider):
# <pubDate>2018-12-06 16:09:03<pubDate>
# 先將字符串轉(zhuǎn)為時間戳
newsTime = time.mktime(time.strptime(item['pub_date'], '%Y-%m-%d %H:%M:%S'))
# 獲取當(dāng)前時間戳
nowTime = time.time()
if (nowTime - newsTime) / (60 * 60) > 1:
raise DropItem("%s ,Not Fresh!" % item) # 超過一個小時是掰,丟棄
return item
class CleanPipeline(object):
"""加工性管道。刪除掉所有的\r\n符號"""
def process_item(self, item, spider):
def clear_html(text):
html = BeautifulSoup(text)
return html.get_text().replace('\n', '')
item['desc'] = clear_html(item['desc'])
return item
class JsonFeedPipeline(object):
"""存儲管道辱匿。存儲到指定的Json文件中去"""
def __init__(self):
self.json_file = open('pipResult.json', 'w')
self.json_file.write("[\n")
def process_item(self, item, spider):
line = json.dumps(dict(item)) + ",\n"
# BeautifulSoup會統(tǒng)一為Unicode編碼键痛,需要重新編碼一下
self.json_file.write(line.encode('utf-8').decode("unicode_escape"))
return item
def close_spider(self, spider):
self.json_file.write("\n]")
self.json_file.close()
文件: setting.py
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'chinanews_crawler.pipelines.PreservationPipeline': 300,
'chinanews_crawler.pipelines.CleanPipeline': 301,
'chinanews_crawler.pipelines.JsonFeedPipeline': 302,
}
這樣,整個爬蟲系統(tǒng)就寫好了匾七,直接在項(xiàng)目根目錄下絮短,運(yùn)行命令 scrapy crawl newsCrawler,即可看到pipResult.json文件昨忆。
<kbd>方法2:全局性指定</kbd>
在setting.py文件中丁频,直接加上以下幾項(xiàng)。
FEED_URI = "result.json" # 保存文件名
FEED_FORMAT = "json" # 保存文件格式
FEED_EXPORT_ENCODING = 'utf-8' # 保存文件的編碼
這樣,整個爬蟲系統(tǒng)就寫好了席里,直接在項(xiàng)目根目錄下叔磷,運(yùn)行命令 scrapy crawl newsCrawler,即可看到result.json文件奖磁。
<kbd>方法3:動態(tài)指定</kbd>
在Scrapy命令中加入-o的輸出參數(shù)即可改基。(本人覺得還不如方法2,每次命令都得加咖为,然后還不利于后人查看代碼秕狰。因此此方法本人并未嘗試)
本項(xiàng)目僅供學(xué)習(xí)參考,所有步驟與本人遇到的坑躁染,本人給予了解釋注釋 鸣哀。因此,請不要都測試同一網(wǎng)站褐啡,以免引起不必要的麻煩诺舔,謝謝。
@copyright Dawn
編輯于:2018/12/6