Scrapy爬蟲實戰 CrawlSpider和Item Loader的使用
網站:
https://tech.china.com/articles/
建立專案:
scrapy startproject scrapyuniversal
之前建立專案,都用scrapy genspider +爬蟲名字+域名的方式,此次要建立CrawlSpider需要使用crawl,建立命令:
scrapy genspider -t crawl china tech.china.com
在專案開始之前,要先來了解一下LinkExtractor,
rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), )
allow是一個正則表示式或者列表,定義了從當前頁面提取的連結哪些是符合要求的。
callback回撥函式,每次從link_extractor中獲取到連結時,被呼叫。接收一個response,返回一個包含Item或者Request物件的列表。
注意:callback中避免屬於parse()作為回撥函式
follow指定根據該規則提取的連結是否需要跟進,如果callback引數為None,follow預設 為True。否則為False。
定義Rule:
Spider會根據每一個Rule來提取這個頁面內的超連結,生成Request.
檢視原始碼:
可以發現所有的資訊都在這個節點內,用正則表示式將文章連結都匹配出來賦值給allow引數。
rules = (
Rule(LinkExtractor(allow='article\/.*\.html',restrict_xpaths='//div[@id="left_side"]//div[@class="con_item"]'),
callback='parse_item',
follow=True),
)
然後找下一頁的連結:
Rule(LinkExtractor(restrict_xpaths='//div[@id="pageStyle"]//a[contains(.,"下一頁")]'
解析頁面:
先定義欄位:
from scrapy import Field, Item
class NewsItem(Item):
title = Field()
text = Field()
datetime = Field()
source = Field()
url = Field()
#站點名稱,區分不同的站點
website = Field()
獲取資料:
def parse_item(self, response):
item = NewsItem()
item['title'] = response.xpath('//h1[@id="chan_newsTitle"]/text()').extract_first()
item['url'] = response.url
item['text'] = ''.join(response.xpath('//div[@id="chan_newsDetail"]//text()').extract()).strip()
item['datetime'] = response.xpath('//div[@id="chan_newsInfo"]/text()').re_first('(\d+-\d+-\d+\s\d+:\d+:\d+)')
item['source'] = response.xpath('//div[@id="chan_newsInfo"]/text()').re_first('來源:(.*)').strip()
item['website'] = '中華網'
yield item
執行之後獲取的結果如下:
用Item Loader,通過
add_xpath()
add_value()
add_css()
實現配置化提取。
def parse_item(self, response):
loader = ChinaLoader(item=NewsItem(), response=response)
loader.add_xpath('title', '//h1[@id="chan_newsTitle"]/text()')
loader.add_value('url', response.url)
loader.add_xpath('text', '//div[@id="chan_newsDetail"]//text()')
loader.add_xpath('datetime', '//div[@id="chan_newsInfo"]/text()', re='(\d+-\d+-\d+\s\d+:\d+:\d+)')
loader.add_xpath('source', '//div[@id="chan_newsInfo"]/text()', re='來源:(.*)')
loader.add_value('website', '中華網')
yield loader.load_item()
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, Join, Compose
定義類:
class NewsLoader(ItemLoader):
#定義TakeFirst(),相當於extract_first()方法
default_output_processor = TakeFirst()
class ChinaLoader(NewsLoader):
#Compose兩個引數
# Join()也是一個Processor,可以把列表拼合成一個字串
# lambda可以將頭尾空白符去掉
text_out = Compose(Join(), lambda s: s.strip())
source_out = Compose(Join(), lambda s: s.strip())
通用配置的抽取
scrapy genspider -t crawl universal universal
新建一個spider,將上文寫的Spider內的屬性抽取出來配置成一個JSON,放到config.json中:
{
"spider": "universal",
"website": "中華網科技",
"type": "新聞",
"index": "http://tech.china.com/",
"settings": {
"USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"
},
"start_urls": {
"type": "dynamic",
"method": "china",
"args": [
5,
10
]
},
"allowed_domains": [
"tech.china.com"
],
"rules": "china"
}
這樣的話,要啟動爬蟲,僅僅需要從配置檔案中讀取後然後載入到Spider中即可,讀取方法程式碼如下:
from os.path import realpath, dirname
import json
def get_config(name):
path = dirname(realpath(__file__)) + '/configs/' + name + '.json'
with open(path, 'r', encoding='utf-8') as f:
return json.loads(f.read())
此時,我們只需要傳入JSON配置檔案 的名稱,就可以獲取配置資訊,入口檔案程式碼如下:
from scrapy.crawler import CrawlerProcess
def run():
# Sys.argv[ ]其實就是一個列表,裡邊的項為使用者輸入的引數,關鍵就是要明白這引數是從程式外部輸入的,而非程式碼本身的什麼地方。
name = sys.argv[1]
custom_settings = get_config(name)
# 爬蟲使用的spider名稱
spider = custom_settings.get('spider', 'universal')
project_settings = get_project_settings()
settings = dict(project_settings.copy())
# 將獲取到的settings配置和專案全域性的settings配置做合併
settings.update(custom_settings.get('settings'))
process = CrawlerProcess(settings)
# 啟動
process.crawl(spider, **{'name': name})
process.start()
if __name__ == '__main__':
run()
解析資料的通用配置,程式碼如下:
# -*- coding: utf-8 -*- from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from scrapyuniversal.items import * from scrapyuniversal.loaders import * from scrapyuniversal.utils import get_config from scrapyuniversal import urls from scrapyuniversal.rules import rules class UniversalSpider(CrawlSpider): name = 'universal' def __init__(self, name, *args, **kwargs): config = get_config(name) self.config = config self.rules = rules.get(config.get('rules')) start_urls = config.get('start_urls') if start_urls: if start_urls.get('type') == 'static': self.start_urls = start_urls.get('value') elif start_urls.get('type') == 'dynamic': # eval() 輸出是輸入的型別 self.start_urls = list(eval('urls.' + start_urls.get('method'))(*start_urls.get('args', []))) self.allowed_domains = config.get('allowed_domains') super(UniversalSpider, self).__init__(*args, **kwargs) def parse_item(self, response): item = self.config.get('item') if item: cls = eval(item.get('class'))() loader = eval(item.get('loader'))(cls, response=response) # 動態獲取屬性配置 for key, value in item.get('attrs').items(): for extractor in value: if extractor.get('method') == 'xpath': loader.add_xpath(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'css': loader.add_css(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'value': loader.add_value(key, *extractor.get('args'), **{'re': extractor.get('re')}) if extractor.get('method') == 'attr': loader.add_value(key, getattr(response, *extractor.get('args'))) yield loader.load_item()
python run.py china 執行。
值得一提的是,引入問題還有執行問題。
引入的時候,會出現一些引入錯誤,這個需要了解相對引入和絕對引入的一些知識。
在執行的時候,使用命令列執行,會出現一系列的細節問題,不過還好,查查資料都可以解決。