scrapy初步解析源码即深度使用

scrapy深度爬虫

——编辑:大牧莫邪

本章内容

  1. 深度爬虫概述
  2. scrapy Spider实现的深度爬虫
  3. scrapy CrawlSpdier实现的深度爬虫
  4. 案例操作

课程内容

1. 深度爬虫概述

  爬虫程序,主要是用与数据采集处理的一种网络程序,在操作过程中针对指定的url地址进行数据请求并根据需要采集数据,但是在实际项目开发过程中,经常会遇到目标url地址数量不明确的情况,如之前的章节中提到的智联招聘项目,不同的岗位搜索到的岗位数量不一定一致,也就意味着每个工作搜索到的工作岗位列表页面的数量不一定一致,爬虫工程师工作可能搜索到了10页,Django工作有可能都索到了25页数据,那么针对这样的数据要全部进行爬取,应该怎么处理呢?答案就是:深度爬虫

  深度爬虫:针对其实url地址进行数据采集,在响应数据中进行数据筛选得到需要进行数据采集的下一波url地址,并将url地址添加到数据采集队列中进行二次爬取..以此类推,一致到所有页面的数据全部采集完成即可完成深度数据采集,这里的深度指代的就是url地址的检索深度。

  深度爬虫可以通过不同的方式实现,在urllib2和requesets模块中通过轮询数据筛选得到目标url地址,然后进行循环爬取数据即可,在scrapy中主要通过两种方式进行处理:

  • 通过Response对象的地址序列和Request对象的请求处理完成深度采集
  • 通过CrawlSpider类型中的请求链接提取规则自动进行深度数据采集处理

2. Spider Request和Response完成数据深度采集

  首先完成深度爬虫之前,先了解Scrapy框架底层的一些操作模式,Scrapy框架运行爬虫项目,默认调用并执行parse()函数进行数据的解析,但是此时已经由框架完成了请求解析调度和下载的过程,那么Scrapy到底做了哪些事情呢?

  我们首先观察一下scrapy.Spider源代码

class Spider(object_ref):
    """Base class for scrapy spiders. All spiders must inherit from this
    class.
    """

    name = None
    custom_settings = None

    # 初始化函数,主要进行程序的名称、起始地址等数据初始化工作
    def __init__(self, name=None, **kwargs):
        if name is not None:
            self.name = name
        elif not getattr(self, ‘name‘, None):
            raise ValueError("%s must have a name" % type(self).__name__)
        self.__dict__.update(kwargs)
        if not hasattr(self, ‘start_urls‘):
            self.start_urls = []
    ...
    ...
    # 程序启动,发送请求的函数
    def start_requests(self):
        cls = self.__class__
        # 默认没有重写直接调用,重写的时候根据子类重写的方式重新定义发送处理方式
        # 默认情况下发送get请求获取数据,如果要发送Post请求可以重写start_reuqests函数进行请求的处理
        if method_is_overridden(cls, Spider, ‘make_requests_from_url‘):
            warnings.warn(
                "Spider.make_requests_from_url method is deprecated; it "
                "won‘t be called in future Scrapy releases. Please "
                "override Spider.start_requests method instead (see %s.%s)." % (
                    cls.__module__, cls.__name__
                ),
            )
            for url in self.start_urls:
                yield self.make_requests_from_url(url)
        else:
            # 没有重写该方法,直接根据初始地址包装请求对象发送请求
            for url in self.start_urls:
                yield Request(url, dont_filter=True)

  我们可以从源代码中查看到,我们定义的爬虫处理类继承的scrapy.Spider类型中,对于初始化的name和start_urls初始地址进行了初始化,然后自动调用start_requests函数包装Request请求对象,然后通过协程调用的方法将请求交给调度器进行后续的处理

这里就需要了解请求对象中到底做了哪些事情?!

  (1) Request对象 Request请求对象是scrapy框架中的核心对象,通过将字符串url地址包装成请求对象交给调度器进行调度管理,之后交给下载模块进行数据采集的操作

Request底层操作部分源码如下:

# scrapy中的Request请求对象
class Request(object_ref):

    # 默认构建时,method="GET"包装的是GET请求的采集方式
    # 参数url:请求地址字符串
    # 参数callback:请求的回调函数
    # 参数headers:默认的请求头
    # 参数body: 请求体
    # 参数cookies:请求中包含的cookie对象
    # 参数encoding:请求编码方式
    def __init__(self, url, callback=None, method=‘GET‘, headers=None, body=None,
                 cookies=None, meta=None, encoding=‘utf-8‘, priority=0,
                 dont_filter=False, errback=None, flags=None):

        self._encoding = encoding  # this one has to be set first
        self.method = str(method).upper()
        self._set_url(url)
        self._set_body(body)
        assert isinstance(priority, int), "Request priority not an integer: %r" % priority
        self.priority = priority

        if callback is not None and not callable(callback):
            raise TypeError(‘callback must be a callable, got %s‘ % type(callback).__name__)
        if errback is not None and not callable(errback):
            raise TypeError(‘errback must be a callable, got %s‘ % type(errback).__name__)
        assert callback or not errback, "Cannot use errback without a callback"
        self.callback = callback
        self.errback = errback

        self.cookies = cookies or {}
        self.headers = Headers(headers or {}, encoding=encoding)
        self.dont_filter = dont_filter

        self._meta = dict(meta) if meta else None
        self.flags = [] if flags is None else list(flags)

  那么在实际操作中,我们通过如下三点详细说明:

  • 如何发送get请求

  直接编写爬虫程序,定义strat_urls中的初始地址和爬虫的name名称,然后重写父类中的parse()函数即可,请求的发送默认就是get()方式进行数据采集:

import scrapy

# 定义自己的爬虫处理类
class MySpider(scrapy.Spider):
    # 定义爬虫名称
    name = ‘myspider‘
    # 定义初始化url地址列表
    start_urls = ("http://www.baidu.com", )
    # 定义域名限制
    allowed_domains = ["baidu.com"]

    # 定义数据处理方式
    def parse(self, response):
        # 数据处理部分
        pass
  • 如何发送post请求

  因为scarpy默认的Request是get方式发送请求,如果要通过post方式发送请求采集数据,需要重新编写start_requests()函数覆盖父类中的请求包装方式

import scrapy 

class MySpider(scrapy.Spider):
    # 定义爬虫名称
    name = ‘myspider‘
    # 定义初始化url地址列表
    start_urls = ("http://www.baidu.com", )
    # 定义域名限制
    allowed_domains = ["baidu.com"]

    # 重写父类请求初始化发送方式
    def start_requests(self, response):
        # 循环初始话地址,发送post请求
        for url in self.start_urls:
            yield scrapy.FormRequest(
                url = url,
                formdata = {post参数字典},
                callback = self.parse_response,
            )

    # 重新编写响应数据处理函数
    def parse_response(self, response):
        # 处理采集到的response数据
        pass

  同时,也可以通过响应对象构建一个POST请求重新发送,如下:

import scrapy

class MySpider(scarpy.Spider):

    # 定义爬虫名称
    name = ‘myspider‘
    # 定义初始化url地址列表
    start_urls = ("http://www.baidu.com", )
    # 定义域名限制
    allowed_domains = ["baidu.com"]

    # 重写父类请求初始化发送方式
    def parse(self, response):
        # 通过响应对象重新构建一个POST请求再次发送
        return scrapy.FormRequest.from_response(
            response,
            formdata = {"post参数字典数据"},
            callback = self.parse_response
        )

    # 重新编写响应数据处理函数
    def parse_response(self, response):
        # 处理采集到的response数据
        pass

(2) Response对象 Response对象在项目中的直接操作并不是很多,参考源代码如下:

# 部分代码
class Response(object_ref):
    def __init__(self, url, status=200, headers=None, body=‘‘, flags=None, request=None):
        self.headers = Headers(headers or {})
        self.status = int(status)       # 响应码
        self._set_body(body)            # 响应体
        self._set_url(url)              # 响应url
        self.request = request          # 请求对象
        self.flags = [] if flags is None else list(flags)

    @property
    def meta(self):
        try:
            return self.request.meta
        except AttributeError:
            raise AttributeError("Response.meta not available, this response "                 "is not tied to any request")

(3)案例操作:模拟CSDN登录

  • 创建爬虫项目
scrapy startproject csdnspider
  • 在csdnspider/csdnspider/spiders/目录中创建csdnspider.py文件,创建爬虫类如下:
# coding:utf-8

import scrapy

class CsdnSpider(scrapy.Spider):
    ‘‘‘
    CSDN登录爬虫处理类
    ‘‘‘
    # 爬虫名称
    name = "cs"
    # 初始登录地址
    start_urls = ["https://passport.csdn.net/account/login"]

    def parse(self, response):

        # 匹配登录流水号
        lt = response.xpath("//form[@id=‘fm1‘]/input[@type=‘hidden‘]/@value").extract()[1]

        # 发送post请求完成登录
        return scrapy.FormRequest.from_response(
            response,
            formdata = {
                "username": "15682808270",
                "password": "DAMUpython2016",
                "lt": lt,
                # "execution": "e2s1",
                # "_eventId": "submit"
            },
            callback=self.parse_response
        )

    def parse_response(self, response):
        # 得到登录后的数据,进行后续处理
        with open("csdn.html", "w") as f:
            f.write(response.body)

(4). 深度采集数据:爬取智联某工作岗位所有页面工作数据

  • 创建爬虫程序
scrapy startproject zlspider
  • 分析请求,定义Item对象
# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy

class ZhilianItem(scrapy.Item):
    ‘‘‘
    定义采集数据的类型,该类型中,会封装采集到的数据
        继承scrapy.Item类型,scrapy框架才会调用内建函数继续自动化操作
    ‘‘‘
    # 通过scrapy.Field()定义属性字段,每个字段都是采集数据的一部分
    job_name = scrapy.Field()
    company = scrapy.Field()
    salary = scrapy.Field()
创建数据库,定义数据表,用于存储数据
# 创建数据库
DROP DATABASE py1709_spider;
CREATE DATABASE py1709_spider DEFAULT CHARSET ‘utf8‘;

USE py1709_spider;

# 创建数据表
CREATE TABLE jobs(
    id INT AUTO_INCREMENT PRIMARY KEY,
    job_name VARCHAR(200),
    company VARCHAR(200),
    salary VARCHAR(50)
);
SELECT COUNT(1) FROM jobs;
SELECT * FROM jobs;
TRUNCATE TABLE jobs;
  • 开发爬虫程序,通过请求对象的自定义包装,完成请求链接[分页连接]跟踪爬取

在zlspider/zlspider/spider/文件夹中,创建zhilianspider.py文件,编辑爬虫程序如下:

# coding:utf-8

# 引入scrapy模块
import scrapy

from ..items import ZhilianItem

class ZhilianSpider(scrapy.Spider):
    ‘‘‘
    智联招聘数据采集爬虫程序
        需要继承scrapy.Spider类型,让scrapy负责调度爬虫程序进行数据的采集
    ‘‘‘
    # name属性:爬虫名称
    name = "zl"
    # allowed_domains属性:限定采集数据的域名
    allowed_domains = ["zhaopin.com"]
    # 起始url地址
    start_urls = [
        #"http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC&kw=%E7%88%AC%E8%99%AB&sm=0&sg=cab76822e6044ff4b4b1a907661851f9&p=1",
        "http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC%2b%E4%B8%8A%E6%B5%B7%2b%E5%B9%BF%E5%B7%9E%2b%E6%B7%B1%E5%9C%B3&kw=python&isadv=0&sg=7cd76e75888443e6b906df8f5cf121c1&p=1",
    ]

    def parse(self, response):
        ‘‘‘
        采集的数据解析函数(响应数据解析函数)
            主要用于进行响应数据的筛选:筛选目标数据分装成Item对象
        :param response:
        :return:
        ‘‘‘

        # # 再次从响应中获取要进行下一次爬取的url地址[其他页面请求]
        # next_page = response.xpath("//div[@class=‘pagesDown‘]/ul/li/a/@href").extract()
        # # 循环处理请求
        # for page in next_page:
        #     page = response.urljoin(page)
        #     # 重新发起请求采集下一组url地址的数据[第一个参数:发起的请求地址,第二个参数:请求数据一旦被采集~交个哪个函数进行处理]
        #     yield scrapy.Request(page, callback=self.parse_response)
        url = response.urljoin(self.start_urls[0])
        yield scrapy.Request(url, callback=self.parse_response)

    def parse_response(self, response):
        # 筛选得到工作列表
        job_list = response.xpath("//div[@id=‘newlist_list_content_table‘]/table[position()>1]/tr[1]")
        # 循环获取采集的字段信息
        for job in job_list:
            # 岗位名称
            job_name = job.xpath("td[@class=‘zwmc‘]/div/a").xpath("string(.)").extract()[0]
            # 公司名称
            company = job.xpath("td[@class=‘gsmc‘]/a").xpath("string(.)").extract()[0]
            # 薪水
            salary = job.xpath("td[@class=‘zwyx‘]").xpath("string(.)").extract()[0]

            # 封装成item对象
            item = ZhilianItem()
            item[‘job_name‘] = job_name
            item[‘company‘] = company
            item[‘salary‘] = salary

            # 通过协程的方式移交给pipeline进行处理
            yield item
        # 再次从响应中获取要进行下一次爬取的url地址[其他页面请求]
        next_page = response.xpath("//div[@class=‘pagesDown‘]/ul/li/a/@href").extract()
        # 循环处理请求
        for page in next_page:
            page = response.urljoin(page)
            # 重新发起请求采集下一组url地址的数据[第一个参数:发起的请求地址,第二个参数:请求数据一旦被采集~交个哪个函数进行处理]
            yield scrapy.Request(page, callback=self.parse_response)
运行测试程序 在终端命令行窗口中,运行程序
scrapy crawl zl

查看数据库中的数据记录

备注:在这样的深度采集数据时,首页数据很有可能会重复,所以,将数据解析函数分成了两个步骤执行,第一步通过parse()函数处理首页地址增加到response.urljoin()中,然后通过parse_response()函数进行实际的数据采集工作,达到首页数据去重的目的!

3. Spider CrawlSpider完成数据深度采集

  Scrapy框架针对深度爬虫,提供了一种深度爬虫的封装类型scrapy.CrawlSpider,我们自己定义开发的爬虫处理类需要继承该类型,才能使用scrapy提供封装的各项深度爬虫的功能

  scrapy.CrawlSpider是从scrapy.Spider继承并进行功能扩展的类型,在该类中,通过定义Url地址的提取规则,跟踪连接地址,从已经采集得到的响应数据中继续提取符合规则的地址进行跟踪爬取数据

部分源代码如下:

class CrawlSpider(Spider):
    rules = ()
    def __init__(self, *a, **kw):
        super(CrawlSpider, self).__init__(*a, **kw)
        self._compile_rules()

    # 1. 调用重写父类的parse()函数来处理start_urls中返回的response对象
    # 2. parse()则将这些response对象再次传递给了_parse_response()函数处理
    # 2.1. _parse_response()函数中设置follow为True,该参数用于打开是否跟进链接提取
    # 3. parse将返回item和跟进了的Request对象
    def parse(self, response):
        return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True)

    # 定义处理start_url中返回的response的函数,需要重写
    def parse_start_url(self, response):
        return []

    # 结果过滤函数
    def process_results(self, response, results):
        return results

    # 从response中抽取符合任一用户定义‘规则‘的链接,并构造成Resquest对象返回
    def _requests_to_follow(self, response):
        if not isinstance(response, HtmlResponse):
            return
        seen = set()

        # 循环获取定义的url地址提取规则
        for n, rule in enumerate(self._rules):
            # 得到所有的提取规则列表
            links = [l for l in rule.link_extractor.extract_links(response) if l not in seen]
            # 使用用户指定的process_links处理每个连接
            if links and rule.process_links:
                links = rule.process_links(links)
            #将链接加入seen集合,为每个链接生成Request对象,并设置回调函数为_repsonse_downloaded()
            for link in links:
                seen.add(link)
                # 构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数
                r = Request(url=link.url, callback=self._response_downloaded)
                r.meta.update(rule=n, link_text=link.text)
                # 对每个Request调用process_request()函数。该函数默认为indentify,即不做任何处理,直接返回该Request.
                yield rule.process_request(r)

    # 采集数据链接处理,从符合规则的rule中提取链接并返回item和request
    def _response_downloaded(self, response):
        rule = self._rules[response.meta[‘rule‘]]
        return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow)

    # 解析response对象,通过callback回调函数解析处理,并返回request或Item对象
    def _parse_response(self, response, callback, cb_kwargs, follow=True):
        # 首先判断是否设置了回调函数。(该回调函数可能是rule中的解析函数,也可能是 parse_start_url函数)
        #如果设置了回调函数(parse_start_url()),那么首先用parse_start_url()处理response对象,
        # 然后再交给process_results处理。返回cb_res的一个列表
        if callback:
            #如果是parse调用的,则会解析成Request对象
            #如果是rule callback,则会解析成Item
            cb_res = callback(response, **cb_kwargs) or ()
            cb_res = self.process_results(response, cb_res)
            for requests_or_item in iterate_spider_output(cb_res):
                yield requests_or_item

        # 如果需要跟进,那么使用定义的Rule规则提取并返回这些Request对象
        if follow and self._follow_links:
            #返回每个Request对象
            for request_or_item in self._requests_to_follow(response):
                yield request_or_item

    # 规则过滤
    def _compile_rules(self):
        def get_method(method):
            if callable(method):
                return method
            elif isinstance(method, basestring):
                return getattr(self, method, None)

        self._rules = [copy.copy(r) for r in self.rules]
        for rule in self._rules:
            rule.callback = get_method(rule.callback)
            rule.process_links = get_method(rule.process_links)
            rule.process_request = get_method(rule.process_request)

    # 链接跟踪全局配置设置
    def set_crawler(self, crawler):
        super(CrawlSpider, self).set_crawler(crawler)
        self._follow_links = crawler.settings.getbool(‘CRAWLSPIDER_FOLLOW_LINKS‘, True)

(1) LinkExtractor链接提取对象

  LinkExtract类型,主要目的是用于定义链接的提取匹配方式

  该类中的方法extract_link()用于从响应对象response中提取符合定义规则的链接

  该类型只会被实例化一次,但是在每次采集得到数据时重复调用

class scrapy.linkextractors.LinkExtractor(
    allow = (),         # 正则表达式,符合规则的链接会提取
    deny = (),          # 正则表达式,负责规则的链接会排除
    allow_domains = (), # 允许的域名
    deny_domains = (),  # 禁止的域名
    deny_extensions = None, # 是否允许扩展
    restrict_xpaths = (),   # xpath表达式,和allow配合使用精确提取数据
    tags = (‘a‘,‘area‘),    # 标签~
    attrs = (‘href‘),       # 指定提取的属性
    canonicalize = True,
    unique = True,          # 唯一约束,是否去重
    process_value = None
)

  上述的参数中,我们可以看到通过一个linkextractors.LinkExtractor对象,可以定义各种提取规则,并且不需要考虑是否会将重复的链接添加到地址列表中

  通过srapy shell做一个简单的测试,首先打开智联工作列表页面,终端命令行执行如下命令:

scrapy shell "http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC%2b%E4%B8%8A%E6%B5%B7%2b%E5%B9%BF%E5%B7%9E%2b%E6%B7%B1%E5%9C%B3&kw=python&isadv=0&sg=5b827b7808f548ad8261595837624f24&p=4"

  此时scrapy就会自动从指定的地址中采集数据,并包含在response变量中,打开了python命令行,导入LinkExtractor类型并定义提取规则:

# 导入LinkExtractor类型
>>> from linkextractors import LinkExtractor
# 定义提取规则,包含指定字符的链接被提取
>>> links = LinkExtractor(allow=(‘7624f24&p=\d+‘))

  接下来,从响应数据中提取符合规则的超链接,执行extract_links()函数如下:

next_urls = links.extract_links(response)

  打印next_urls,得到如下结果:

[Link(url=‘http://sou.zhaopin.com/jobs/searchresult.ashx
?jl=%E5%8C%97%E4%BA%AC%2b%E4%B8%8A%E6%B5%B7%2b%E5%B9%BF%E
5%B7%9E%2b%E6%B7%B1%E5%9C%B3&kw=python&isadv=0&sg=5b827b7
808f548ad8261595837624f24&p=4‘,text=u‘\u767b\u5f55‘, frag
ment=‘‘, nofollow=True), Link(url=‘http://sou.zhaopin.com
/jobs/searchresult.ashx?j
l=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b%e5%b9%bf%e5%
b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0&sg=5b827b780
8f548ad8261595837624f24&p=3‘, text=u‘\u4e0a\u4e00\u9875‘,
fragment=‘‘, nofollow=False), Link(url=‘http://sou.zhaopi
n.com/jobs/searchresult.ashx?j
l=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b%e5%b9%bf%e5%b
7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0&sg=5b827b7808
f548ad8261595837624f24&p=1‘, text=‘1‘, fragment=‘‘, nofoll
ow=False), Link(url=‘http://sou.zhaopin.com/jobs/searchre
sult.ashx?jl=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b%e
5%b9%bf%e5%b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0&s
g=5b827b7808f548ad8261595837624f24&p=2‘, text=‘2‘, fragme
nt=‘‘, nofollow=False), Link(url=‘http://sou.zhaopin.com/
jobs/searchresult.ashx?j
l=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b%e5%b9%bf%e5%
b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0&sg=5b827b780
8f548ad8261595837624f24&p=5‘, text=‘5‘, fragment=‘‘, nofo
llow=False), Link(url=‘http://sou.zhaopin.com/jobs/search
result.ashx?jl=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b
%e5%b9%bf%e5%b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0
&sg=5b827b7808f548ad8261595837624f24&p=6‘, text=‘6‘, frag
ment=‘‘, nofollow=False), Link(url=‘http://sou.zhaopin.co
m/jobs/searchresult.ashx?j
l=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b%e5%b9%bf%e5%
b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0&sg=5b827b780
8f548ad8261595837624f24&p=7‘, text=‘7‘, fragment=‘‘, nofo
llow=False), Link(url=‘http://sou.zhaopin.com/jobs/search
result.ashx?jl=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b
%e5%b9%bf%e5%b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0
&sg=5b827b7808f548ad8261595837624f24&p=8‘, text=‘8‘, frag
ment=‘‘, nofollow=False), Link(url=‘http://sou.zhaopin.co
m/jobs/searchresult.ashx?j
l=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b%e5%b9%bf%e5%
b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0&sg=5b827b780
8f548ad8261595837624f24&p=9‘, text=‘...‘, fragment=‘‘, no
follow=False)]

  我们可以很直观的看到,所有符合规则的连接全部被提取了出来

(2) Rule规则对象

  Rule对象是链接操作规则对象,主要定义了对于LinkExtractor类型提取的超链接url地址的操作行为,可以在一个爬虫程序中定义多个Rule对象,包含在一个rules列表中即可

class scrapy.spiders.Rule(
        # LinkExtractor对象
        link_extractor,
        # 回调函数,得到数据库之后调用的函数
        callback = None,
        # 回调函数调用时传递的参数列表
        cb_kwargs = None,
        # 是否从返回的响应数据中根据LinkExtractor继续提取,一般选择True
        follow = None,
        # 从LinkExtractor中提取的连接,会自动调用该选项指定的函数,用来进行超链接的筛选
        process_links = None,
        # 指定每个请求封装处理时要调用的函数
        process_request = None
)

(3) 案例操作

  智联招聘深度爬虫操作案例:

  • 创建爬虫项目

    scrapy startproject zhilianspider2
  • 创建爬虫程序 在zhilianspider2/zhilianspider2/spiders/目录中创建zhilianspider.py文件,编辑如下:

    # coding:utf-8
    
    # 引入CrawlSpider, Rule, LinkExtractor模块
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spider import CrawlSpider, Rule
    
    class ZhilianSpider(CrawlSpider):
        """
        智联招聘深度爬虫处理类
        继承scrapy.spiders.CrawlSpider类型
        """
        # 定义爬虫名称
        name = "cs2"
        # 定义域名限制
        allowed_domains = ["zhaopin.com"]
        # 定义起始地址
        start_urls = ("http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%8C%97%E4%BA%AC%2b%E4%B8%8A%E6%B5%B7%2b%E5%B9%BF%E5%B7%9E%2b%E6%B7%B1%E5%9C%B3&kw=python&isadv=0&sg=5b827b7808f548ad8261595837624f24&p=1",)
    
        # 定义提取规则
        links = LinkExtractor(
            allow=("5837624f24&p=\d+")
        )
    
        # 定义操作规则
        rules = [
            # 定义一个操作规则
            Rule(links, follow=True, callback=‘parse_response‘),
        ]
    
        # 定义数据处理函数
        def parse_response(self, response):
            # 提取数据
            job_list = response.xpath("//div[@id=‘newlist_list_content_table‘]/table[@class=‘newlist‘][position()>1]")
            # 循环筛选数据
            for job in job_list:
                job_name = job.xpath("tr[1]/td[@class=‘zwmc‘]/div/a").xpath("string(.)").extract()[0]
    
                print job_name
    
            print("*************************************************")

  在终端命令行中执行如下命令运行爬虫程序

scrapy crawl cs2

  可以在控制台看到具体的爬取信息,对于提取的数据全部进行了跟踪处理

..
[scrapy.core.engine] DEBUG: Crawled (200) <GET http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%e5%8c%97%e4%ba%ac%2
b%e4%b8%8a%e6%b5%b7%2b%e5%b9%bf%e5%b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python&isadv=0&sg=5b827b7808f548ad8261595837624f24&p=13> (referer: http
://sou.zhaopin.com/jobs/searchresult.ashx?jl=%e5%8c%97%e4%ba%ac%2b%e4%b8%8a%e6%b5%b7%2b%e5%b9%bf%e5%b7%9e%2b%e6%b7%b1%e5%9c%b3&kw=python
&isadv=0&sg=5b827b7808f548ad8261595837624f24&p=9)

....
图像算法工程师
软件测试工程师
********************************************************************
软件测试经理
高级软件测试工程师

......
 ‘scheduler/enqueued/memory‘: 17,
 ‘spider_exceptions/IOError‘: 3,
 ‘spider_exceptions/UnicodeEncodeError‘: 1,
 ‘start_time‘: datetime.datetime(2018, 1, 17, 4, 33, 38, 441000)}
2018-01-17 12:35:56 [scrapy.core.engine] INFO: Spider closed (shutdown)

原文地址:https://www.cnblogs.com/AbnerLc/p/12046467.html

时间: 2024-10-07 20:44:08

scrapy初步解析源码即深度使用的相关文章

转:[gevent源码分析] 深度分析gevent运行流程

[gevent源码分析] 深度分析gevent运行流程 http://blog.csdn.net/yueguanghaidao/article/details/24281751 一直对gevent运行流程比较模糊,最近看源码略有所得,不敢独享,故分享之. gevent是一个高性能网络库,底层是libevent,1.0版本之后是libev,核心是greenlet.gevent和eventlet是亲近,唯一不同的是eventlet是自己实现的事件驱动,而gevent是使用libev.两者都有广泛的应

mybatis源码级别深度剖析

mybatis 3.x源码深度解析与最佳实践 Mybatis源码解析优秀博文 原文地址:https://www.cnblogs.com/yixiu868/p/11295523.html

SPRING多个占位符配置文件解析源码研究--转

原文地址:http://www.cnphp6.com/archives/85639 Spring配置文件: <context:property-placeholder location="classpath:/settings.properties" /> <context:property-placeholder location="classpath:/conf.properties"/> settings.properties redi

FFmpeg中HLS文件解析源码

不少人都在找FFmpeg中是否有hls(m3u8)解析的源码,其实是有的.就是ffmpeg/libavformat/hlsproto.c,它依赖的文件也在那个目录中. /* * Apple HTTP Live Streaming Protocol Handler * Copyright (c) 2010 Martin Storsjo * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute

mybatis之XML解析源码分析

一直想知道mybatis是如何解析xml文件的,今天认真看了下源码,这里记录一下 这里是用mybatis-spring的SqlSessionFactoryBean当作的入口,mybatis-spring其实很简单,源码也就几个看看就懂了,代理了一下而已没啥东东. 1.解析spring的配置 不过很多参数都是spring中来处理了,所以mybatis-spring没有先parse而是先加载了配置文件 依次是 typeAliasesPackage typeAliases Plugins typeHa

REST Framework组件的解析源码

首先我们要知道解析器的作用 解析器就是对你请求体中的数据进行反序列化.封装 把你的所有的请求数据都封装在request.data中 以后就在request.data中获取数据 我们先导入rest_framework的解析器 from rest_framework.parsers import JSONParser,FormParser from rest_framework.parsers import JSONParser,FormParser class PaserView(APIView)

Java注解及其原理以及分析spring注解解析源码

注解的定义 注解是那些插入到源代码中,使用其他工具可以对其进行处理的标签. 注解不会改变程序的编译方式:Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令. 在Java中,注解是被当做一个修饰符来使用的(修饰符:如public.private) 注解的常用用法:1. 附属文件的自动生成,例如bean信息类. 2. 测试.日志.事务等代码的自动生成. 单元测试例子: import org.junit.Test; public class SomeTest { @Test publi

ORB-SLAM2初步(源码逻辑分析)

今天主要是梳理一下ORB-SLAM2源码的逻辑关系,GitHub和泡泡机器人上有很好的注释版本(吴博),大神请(轻)板砖. 一.文件 如图所示,Examples里面存放的分别是基于单目.双目.RGBD的实例程序,一般都是基于TUM等数据库,还有一个ROS版本的ORB-SLAM2,以及一个应用与AR的Demo程序: include文件夹存放的是头文件,ORB-SLAM2可以被当作一个库来使用,很多函数都可以直接调用: src文件夹存放的是和include对应的源文件,包括主要的Tracking.L

ios发射子弹游戏案例解析源码

这个游戏是本人在ios教程网ios.662p.com发布的一款游戏源码,喜欢的朋友可以看一下,游戏介绍:游戏非常简单,通过发射子弹,将怪物射死.注意还有大怪物出没.当你打死的怪物越来越多时,游戏难度也会逐渐变大,使用SpriteKit编写的一款简单游戏,对于iOS游戏开发新手,是一个不错的demo.Shoot the monsters for win! <ignore_js_op> 详细说明:http://ios.662p.com/thread-1676-1-1.html