scrapy——高级深度操作

一:概述

  • 深度爬虫:针对其实url地址进行数据采集,在响应数据中进行数据筛选得到需要进行数据采集的下一波url地址,并将url地址添加到数据采集队列中进行二次爬取,以此类推
  • 深度爬虫可以通过不同的方式实现,在urllib2和requesets模块中通过轮询数据筛选得到目标url地址,然后进行循环爬取数据即可,在scrapy中主要通过两种方式进行处理:
    1.通过Response对象的地址序列和Request对象的请求处理完成深度采集
    2.通过CrawlSpider类型中的请求链接提取规则自动进行深度数据采集处理

二:深度爬虫的应用

1.通过Response对象和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请求
import scrapy

#get请求
class GetSpider(scrapy.Spider):
    #一般情况下爬虫默认的为get请求方式

    #定义爬虫名称
    name = 'getspider'

    #定义限制访问域名
    allowed_domains = ['baidu.com']

    #定义起始url地址
    start_urls = [
        'http://www.baidu.com'
    ]


    def parse(self, response):
        #进行响应数据的处理
        '''

        起始请求的数据采集,由scrapy框架自动完成
                爬虫——起始地址url
                    ——scrapy.Spider——Request()请求

        :param response:
        :return:
        '''
        pass


  • post请求
#post请求
class PostSpider(scrapy.Spider):
    '''
    post请求操作爬虫
    '''

    name = 'postspider'
    allowed_domains = ['renren.com']

    start_urls = [
        'http://www.renren.com/login'
    ]

    def start_requests(self):
        '''
        重写start_requests()函数,发送自定义请求
        :return:
        '''
        return scrapy.FormRequest(
            self.start_urls[0],
            formdata={'username':'admin', 'password':123456},
            callback=self.parse_response
        )

    def parse_response(self, response):
        #用来专门处理post请求得到的响应数据
        pass


ps:由于scrapy框架默认的Request是get请求,想要发送post的请求,需要重写start_requests()函数覆盖父类中原有的请求方法

**Response对象在项目中直接操作的并不是很多


  • 小结
    案例操作:深度爬取xx招聘网站的xx岗位信息
#1.创建项目
scrapy startproject myspider
#2.分析请求地址页面,想要获取的数据,定义字段,封装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
#分析字段
job = 工作名称
company = 公司名称
salary = 薪水

class ZhilianItem(scrapy.Item):
    #定义属性字段
    job = scrapy.Field()
    company = scrapy.Field()
    salary = scrapy.Field()

#3.创建数据库表来存储数据
create table jobs(
      id int auto_increment primary key,
      job varchar(200),
      company varchar(200),
      salary = varchar(50)
);

#4.开发爬虫程序

# -*- coding:utf-8 -*-

import scrapy

from .. import items

class ZhiLian(scrapy.Spider):
    #定义爬虫的名称,用于在命令中调用
    name = 'zl'
    #定义域名限制,只能爬取xxx域名下的数据
    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&p=1',
    
    )

    def parse(self, response):

        '''
        采集到数据后,自动执行的函数,主要进行如下功能
                数据筛选——》封装Item对象——》传递数据给Pipelines
                    采集到的数据封装在response
        '''

        #提取所有的工信息列表selecor列表
        job_list = response.xpath("//div[@id='newlist_list_content_table']/table[position()>1]/tr[1]")
        for select in job_list:
            job = select.xpath("td[@class='zwmc']/div/a").xpath('string(.)').extract()[0]
            company = select.xpath("td[@class='gsmc']/a/text()").extract()[0]
            salary = select.xpath("td[@class='zwyx']/text()").extract()[0]
            print job
            print company
            print salary

            #封  装成item对象
            item = items.ZhilianItem()
            item['job'] = job
            item['company'] = company
            item['salary'] = salary

            #将本次生成的item对象交给pipeline进行处理
            yield item

        #深度采集,爬取下一页
        page_list = response.xpath('//div[@class="pagesDown"]/ul/li/a/@href').extract()
        print ('################')
        print page_list
        print ('################')
        for next_page in page_list:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)



#5.数据处理:开发管道模块
在pipelines.py模块中定义处理Item数据的pipelines,将数据存储到数据库中

#导入数据库引擎对象
from sqlalchemy import create_engine

#导入会话构建对象
from sqlalchemy.orm import sessionmaker

#替换MySQldb模块
import pymysql
pymysql.install_as_MySQLdb()

class ZhilianPipeline(object):
    #处理智联招聘数据的pipeline,负责最终的数据验证和数据存储

    def __init__(self):
        '''
        初始化对象数据:可以用于初始化资源
            如:打开文件,打开数据库连接等操作
        '''
        #创建连接数据库引擎
        self.engine = create_engine('mysql://root:123456@localhost/spider_zhilian?charset=utf8')
        Session = sessionmaker(bind=self.engine)
        self.session = Session()

    def open_spider(self, spider):
        '''
        爬虫开启时需要调用函数,经常用于数据的初始化
        :param spider:
        :return:
        '''
        pass

    def close_spider(self,spider):
        '''
        爬虫程序自动关闭时调用函数
        经常用于做一些资源回收工作,如关闭和数据库的连接
        :return:
        '''
        self.session.close()

    def process_item(self, item, spider):
        '''
        该函数会在爬虫采集并封装好的Item对象时自动调用
        函数针对item数据进行验证和存储
        :param item:
        :param spider:
        :return:
        '''
        #定义sql语句
        sql = 'insert into job(job, company, salary) values( "%s", "%s", "%s")'\
                % (item['job'], item['company'], item['salary'])
        #执行sql语句
        self.session.execute(sql)
        #提交数据
        self.session.commit()


#6.在setting.py设置模块中注册pipeline

ITEM_PIPELINES = {
   # 'myspider.pipelines.MyspiderPipeline': 300,
   'myspider.pipelines.ZhilianPipeline': 300,
}
#7.启动项目
    命令行:scrapy  crawl zl

运行程序数据库里已经有数据了,是不是很easy


image.png

2.Spider CrawlSpider完成数据深度爬取

  • scrapy框架对于深度爬虫,提供了一种封装类scrapy.CrawlSpider,当我们开发时继承这个类,就能使用scrapy框架封装好的各种深度爬虫功能
  • scrapy.CrawlSpider是从scrapy.Spider继承并进行功能扩展的类,主要通过定义url地址提取规则,跟踪链接地址,从而进行深度的数据采集

(1).查看CrawlSpider的部分源码,可以帮助你理解

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)



(2).LinkExtractor链接提取对象:主要目的用于定义链接提取匹配方式

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
)


ps:我们可以在终端命令执行  scrapy shell + url地址
测试url地址是否有效

# 导入LinkExtractor类型
>>> from linkextractors import LinkExtractor
# 定义提取规则,包含指定字符的链接被提取
>>> links = LinkExtractor(allow=('7624f24&p=\d+'))
#从响应数据中提取符合规则的超链接,执行extract_links()函数
next_urls = links.extract_links(response)



(3).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  
)

(4).实战演练:爬取招聘信息

#1.创建项目
      scrapy startproject  myspider2

#2.分析请求地址页面,想要获取的数据,定义字段,封装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
#分析字段
job = 工作名称
company = 公司名称
salary = 薪水

class ZhilianItem(scrapy.Item):
    #定义属性字段
    job = scrapy.Field()
    company = scrapy.Field()
    salary = scrapy.Field()
#3.爬虫程序开发
'''
基于scrapy.spider.CrawlSpider的深度爬虫操作
'''
#引入模块
from scrapy.spider import CrawlSpider, Rule
#引入链接提取模块
from scrapy.linkextractors import LinkExtractor
from .. items import ZhaopinItem

class ZhaopinSpider(CrawlSpider):
    #定义爬虫名称
    name = 'zp'
    #限制域名
    allowed_domains = ['zhaopin.com']
    #定义起始url地址
    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=7cd76e75888443e6b906df8f5cf121c1&p=1",
    ]
    #定义链接提取规则:根据url地址分析得到
    link_extractor = LinkExtractor(
        allow = (r"e75888443e6b906df8f5cf121c1&p=\d+")
    )
    #定义链接操作规则
    rules = [
        Rule(link_extractor, follow = True, callback = 'parse_response'),
    ]
    #注意:在这里不能重写parse()函数,因为父类中已经重写过parse()函数,如果我们再次重写该函数,深度采集数据就会失效
   
   #处理响应数据
    def parse_response(self, response):
        #提取当前页面所有需要的数据
        job_list = response.xpath("//div[@id='newlist_list_content_table']/table[position()>1]/tr[1]")
        for jobs in job_list:
            job = jobs.xpath("td[@class='zwmc']/div/a").xpath("string(.)").extract()[0]
            company = jobs.xpath("td[@class='gsmc']/a/text()").extract()[0]
            salary = jobs.xpath("td[@class='zwyx']/text()").extract()[0]
            print(job)
            print(company)
            print(salary)


ps:这里我们已经取到了想要的数据了,只是没有存储到数据库里、其实数据存储跟上面的案例数据存储方式一样,可以直接copy过来使用

#4.启动项目
      scrapy crawl zp

三:总结

经过对深度采集数据的两种方式的介绍,那么下面该干什么呢?

欲知后事如何,且看下回分解

(欢迎加入Python交流群:930353061。人生苦短,我用python!!!)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容