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 运行。

值得一提的是,引入问题还有运行问题。

引入的时候,会出现一些引入错误,这个需要了解相对引入和绝对引入的一些知识。

在运行的时候,使用命令行运行,会出现一系列的细节问题,不过还好,查查资料都可以解决。

猜你喜欢

转载自blog.csdn.net/qq_39138295/article/details/84073051
今日推荐