在 Day 13 的內容中,我們有把回文都爬回來,今天會把相關的邏輯都移植到 Scrapy,同時整理一下目前的程式碼。相關的程式碼都放在 gist 上了,接下來會分別做說明。
IthomeArticleItem
類別中加了一個 _id
欄位來使用 MongoDB 新增資料後產生的識別值。
另外新增了一個 IthomeReplyItem
類別,用來儲存回文資料。
class IthomeReplyItem(scrapy.Item):
_id = scrapy.Field()
article_id = scrapy.Field()
author = scrapy.Field()
publish_time = scrapy.Field()
content = scrapy.Field()
之前回文都是用
response
表示,為了避免跟 Scrapy 的回應物件搞混,這邊都改用reply
。
新增了一個 AbstractMongoPipeline
類別,把啟動與關閉 MongoDB 連線的邏輯都抽取出來,讓其他要使用 MongoDB 的 Pipelines 元件繼承,各元件只需要定義對應的 collection_name
即可。
import pymongo
class AbstractMongoPipeline(object):
collection_name = None
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
self.collection = self.db[self.collection_name]
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE')
)
def close_spider(self, spider):
self.client.close()
實作處理原文和回文的元件可以分別參考 gist 中的原始碼:原文元件、回文元件。
要特別注意,在同一個專案中如果有不同 Pipelines 元件分別處理不同的 Items,要額外判斷收到的是不是預期要處理的類別,如果不是就要直接回傳交給後面的元件處理。
def process_item(self, item, spider):
# 只有在收到原文的 Item 時才處理
if type(item) is items.IthomeArticleItem:
最後要記得把元件加到執行序列中:
ITEM_PIPELINES = {
'ithome_crawlers.pipelines.IthomeCrawlersPipeline': 300,
'ithome_crawlers.pipelines.IthomeArticlePipeline': 400,
'ithome_crawlers.pipelines.IthomeReplyPipeline': 410,
}
最後在爬蟲中加入 parse_reply(self, response, article_id)
方法,用來剖析回文資料。在處理原文方法的結尾處呼叫剖析回文的方法。
def parse_article(self, response):
# ...剖析原文資料
yield article
'''
瀏覽數小於 20 的文章會被移除
就不會有新增後的識別值
'''
if '_id' in article:
'''
上一行執行後資料已更新到資料庫中
因為是同一個物件參照
可以取得識別值
'''
article_id = article['_id']
'''
因為 iTHome 原文與回文都是在同一個畫面中
剖析回文時使用原本的 response 即可
否則這邊需要再回傳 Request 物件
yield scrapy.Request(url, callback=self.parse_reply)
'''
yield from self.parse_reply(response, article_id)
接著執行看看吧!