文章目录

  • 第十八章 腾讯招聘案例
    • 1. 腾讯招聘案例
    • 2. 代码实现
      • 2.1 配置项目
      • 2.2 解析数据
      • 2.3 翻页处理
      • 2.4 获取详情页信息
    • 3. 古诗词网补充
      • 3.1 验证是否在源码中
      • 3.2 获取详情页地址
      • 3.3 项目补充
      • 3.4 发起请求
      • 3.5 定义解析详情页方法
    • 4. log补充
    • 5. scrapyshell讲解
    • 6. settings补充
    • 7. 总结
      • 7.1 scrapy项目结构
        • 7.1.1 pipelines
        • 7.1.2 items
        • 7.1.3 settings
        • 7.1.4 spider

第十八章 腾讯招聘案例

上节课我们学习了一个案例,操作了一下scrapy的流程,后面加了一个翻页处理。找到下一页的链接,找到链接之后生成一个scrapy.Request()对象,yield给引擎来处理。这是处理翻页的代码:

next_href = response.xpath('//a[@id="amore"]/@href').extract_first()
 if next_href:
     next_url = response.urljoin(next_href) # 补全url 补全 1 拼串 2 urljoin()
     request = scrapy.Request(next_url)
     yield request

但在这里还有些知识点需要补充。我们按住Ctrl点击Request方法,进去看一眼。

class Request(object_ref):

    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, cb_kwargs=None):

        self._encoding = encoding  # this one has to be set first
        self.method = str(method).upper()
        self._set_url(url)
        self._set_body(body)
        if not isinstance(priority, int):
            raise TypeError(f"Request priority not an integer: {priority!r}")
        self.priority = priority

        if callback is not None and not callable(callback):
            raise TypeError(f'callback must be a callable, got {type(callback).__name__}')
        if errback is not None and not callable(errback):
            raise TypeError(f'errback must be a callable, got {type(errback).__name__}')
        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._cb_kwargs = dict(cb_kwargs) if cb_kwargs else None
        self.flags = [] if flags is None else list(flags)

    @property
    def cb_kwargs(self):
        if self._cb_kwargs is None:
            self._cb_kwargs = {}
        return self._cb_kwargs

    @property
    def meta(self):
        if self._meta is None:
            self._meta = {}
        return self._meta

    def _get_url(self):
        return self._url

    def _set_url(self, url):
        if not isinstance(url, str):
            raise TypeError(f'Request url must be str or unicode, got {type(url).__name__}')

        s = safe_url_string(url, self.encoding)
        self._url = escape_ajax(s)

        if (
            '://' not in self._url
            and not self._url.startswith('about:')
            and not self._url.startswith('data:')
        ):
            raise ValueError(f'Missing scheme in request url: {self._url}')

    url = property(_get_url, obsolete_setter(_set_url, 'url'))

    def _get_body(self):
        return self._body

    def _set_body(self, body):
        if body is None:
            self._body = b''
        else:
            self._body = to_bytes(body, self.encoding)

    body = property(_get_body, obsolete_setter(_set_body, 'body'))

    @property
    def encoding(self):
        return self._encoding

    def __str__(self):
        return f"<{self.method} {self.url}>"

    __repr__ = __str__

    def copy(self):
        """Return a copy of this Request"""
        return self.replace()

    def replace(self, *args, **kwargs):
        """Create a new Request with the same attributes except for those
        given new values.
        """
        for x in ['url', 'method', 'headers', 'body', 'cookies', 'meta', 'flags',
                  'encoding', 'priority', 'dont_filter', 'callback', 'errback', 'cb_kwargs']:
            kwargs.setdefault(x, getattr(self, x))
        cls = kwargs.pop('cls', self.__class__)
        return cls(*args, **kwargs)

    @classmethod
    def from_curl(cls, curl_command, ignore_unknown_options=True, **kwargs):
        """Create a Request object from a string containing a `cURL
        <https://curl.haxx.se/>`_ command. It populates the HTTP method, the
        URL, the headers, the cookies and the body. It accepts the same
        arguments as the :class:`Request` class, taking preference and
        overriding the values of the same arguments contained in the cURL
        command.

        Unrecognized options are ignored by default. To raise an error when
        finding unknown options call this method by passing
        ``ignore_unknown_options=False``.

        .. caution:: Using :meth:`from_curl` from :class:`~scrapy.http.Request`
                     subclasses, such as :class:`~scrapy.http.JSONRequest`, or
                     :class:`~scrapy.http.XmlRpcRequest`, as well as having
                     :ref:`downloader middlewares <topics-downloader-middleware>`
                     and
                     :ref:`spider middlewares <topics-spider-middleware>`
                     enabled, such as
                     :class:`~scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware`,
                     :class:`~scrapy.downloadermiddlewares.useragent.UserAgentMiddleware`,
                     or
                     :class:`~scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware`,
                     may modify the :class:`~scrapy.http.Request` object.

        To translate a cURL command into a Scrapy request,
        you may use `curl2scrapy <https://michael-shub.github.io/curl2scrapy/>`_.

       """
        request_kwargs = curl_to_request_kwargs(curl_command, ignore_unknown_options)
        request_kwargs.update(kwargs)
        return cls(**request_kwargs)

我们看init方法里面,第一个参数是url,这是我们熟悉的,后面还有一个参数是callback=None,这是一个回调函数,当我们想整个scrapy流程的时候,当我们的spider(爬虫程序)处理完了之后,通过爬虫中间件,假如你还有链接地址,这个链接地址又通过中间件给了引擎,由引擎再给调度器,调度器再给下载器,又走了这么一圈的流程。callback就是一个回调函数。怎么用呢,我们看这个代码:

next_href = response.xpath('//a[@id="amore"]/@href').extract_first()
       if next_href:
           next_url = response.urljoin(next_href) # 补全url 补全 1 拼串 2 urljoin()
          #  request = scrapy.Request(next_url)
         #  yield request
         yield scrapy.Request(url=next_url,callback=self.parse
            )  # 后面加上了callback参数,调用了parse对象,就是把url又给了parse对象去处理。

我们把后两行的代码注释一下。后面是补全的代码。其实,后面的参数不加也可以,只不过加上逻辑看起来更清晰。我们在pipelines里面加两行代码“爬虫开始”,“爬虫结束”便于观察:

import json

class GswPipeline:
    def open_spider(self,spider):
        print('爬虫开始')
        self.fp = open('gsw02.txt','w',encoding='utf-8')  # 新建一个文本来验证结果

    def process_item(self, item, spider):
        item_json = json.dumps(dict(item),ensure_ascii=False)
        self.fp.write(item_json+'\n')

        return item
    def close_spider(self,spider):
        print('爬虫结束')
        self.fp.close()

我们start运行一下:

D:\Python38\python.exe D:/work/爬虫/Day19/my_code/Demo/gsw/gsw/spiders/start.py
爬虫开始
爬虫结束

Process finished with exit code 0

目录里出现了新建的文件夹:

除了callback,后面还有一个meta=None。上次我们的古诗文案例里,如果我们还需要点击详情页,进入查看诗文详情,那么如果我们要爬取详情页的内容,就需要向详情页发起请求,我们就需要一个详情页的url。下面我们先暂时放下,我们先讲另一个案例。

1. 腾讯招聘案例

https://careers.tencent/search.html这是腾讯官网的招聘平台。

我们要爬取的是工作岗位信息。下面同样要做一个翻页的处理。

按照以往的逻辑,我们先搞定一页,然后再做翻页处理。上一次,我们是通过生成scrapy.Request对象yield给引擎来处理的。这个案例我们该怎么做呢?如果我们右键检查翻页按钮的话得到的是这个东西:

这个再用原来的思路显然是行不通的,没有url。
我们多点击几页,找一下url的规律:

# https://careers.tencent/search.html 第一页
# https://careers.tencent/search.html?index=2 第二页
# https://careers.tencent/search.html?index=3 第三页
# https://careers.tencent/search.html?index=4 第四页

实际上index=1时就是第一页。不过我们还需要弄清楚,我们要的数据在不在数据源码中,如果不在,及时我们发起了请求,也得不到结果。于是我们右键网页,选择源码,我们Ctrl+F调出搜索栏,搜索一个页面上显示的岗位,比如:15583-手游3D场景设计师(深圳)

很遗憾,没有。而且我们看到,这个源码的数据很少。说明很多数据都是后来加载的。
如果嫌这个没有说服力,我们可以右键,检查,在第一个请求中查看response.Ctrl+F调出搜索:

我们输入了页面中的一个内容,却一个结果也没有发现。这证明,这些数据是动态数据。处理方法是两种方式。一,selenium。二,分析数据接口。直接在XHR里面去找。

我们发现了结果。
那既然数据不在第一个搜索结果里,我们就不能用上面的url作为起始url。而是在我们后面的XHR里面找到的那个“Query?timestamp。。。”里的url才可以作为起始url

我们把这个url地址复制下来:

Request URL: https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613034362228&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn

我们再点开一页,找一下规律:

Request URL: https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613035257107&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=2&pageSize=10&language=zh-cn&area=cn

我们很快发现了规律pageIndex=1的值在改变。只要动态替换这个值就可以了。好的,到此我们找到了。

2. 代码实现

创建项目:

D:\work\爬虫\Day20\my_code>scrapy startproject tencent
New Scrapy project 'tencent', using template directory 'd:\python38\lib\site-packages\scrapy\templates\project', created in:
    D:\work\爬虫\Day20\my_code\tencent

You can start your first spider with:
    cd tencent
    scrapy genspider example example

接下来:

cd tencent
D:\work\爬虫\Day20\my_code>cd tencent

D:\work\爬虫\Day20\my_code\tencent>

接下来我们创建一个爬虫项目:

D:\work\爬虫\Day20\my_code\tencent>scrapy genspider hr tencent
Created spider 'hr' using template 'basic' in module:
  tencent.spiders.hr

创建成功。

2.1 配置项目

我们双击一下hr项目,我们先把start_url改一下:

import scrapy


class HrSpider(scrapy.Spider):
    name = 'hr'
    allowed_domains = ['tencent']
    start_urls = ['https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613034362228&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn
']

    def parse(self, response):
        pass

这样我们就可以拿到一个正确的response结果,我们进行解析就可以了。我们把拿到的字符串转成json数据:

import scrapy
import json

class HrSpider(scrapy.Spider):
    name = 'hr'
    allowed_domains = ['tencent']
    start_urls = ['https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613034362228&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn']

    
    def parse(self, response):
        data = json.loads(response.text)
        print(data)

我们打开settings,加上LOG_LEVEL = ‘WARNING’
Robot = False
后面的headers打开,加上一个user-agent
然后再创建一个start文件

from scrapy import cmdline
cmdline.execute('scrapy crawl hr'.split())

我们直接start一下:

{'Code': 200, 'Data': {'Count': 8643, 'Posts': [{'Id': 0, 'PostId': '1316995743826845696', 'RecruitPostId': 67328, 'RecruitPostName': '33851-UE4海外项目技术支持工程师', 'CountryName': '中国', 'LocationName': '上海', 'BGName': 'IEG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '参与管理中心与海外项目研发团队的技术相关沟通;\n与执行制作人紧密配合,与海外项目研发团队保持日常沟通,确保项目的技术层面工作健康推进,预警新产生的风险,协调内外部资源提供支持;\n做好海外项目研发团队阶段性提交版本和代码的整理工作,并提取能够横向分享的优秀内容,为各个研发团队提供更加全面的技术/系统/工具参考;\n参与建立管理中心独有的可共享资源库。', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1316995743826845696', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1328887379171221504', 'RecruitPostId': 68744, 'RecruitPostName': '17759-腾讯视频电商招商&供应链建设', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '产品', 'Responsibility': '主要负责公司直播带货BD品牌货品,搭建成熟的供应商体系; \n与商家沟通,商家异议处理,负责品牌的商务谈判和条款,维护好商家合作关系; \n确保直播前样品准备到位,完成相关流程等; \n配合供应相关部门的货品需求 ;\n可以根据商业化规则为节目定制特色商品并开发。\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328887379171221504', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1328921972448436224', 'RecruitPostId': 68769, 'RecruitPostName': '17759-腾讯视频电商数据分析师', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '战略与投资', 'Responsibility': '负责腾讯视频直播电商和短视频电商数据分析工作,具体如下:\n明确电商业务线及项目的业务方向,构建业务指标体系,建立和完善日常业务报告机制;\n及时、准确、完整地汇总腾讯视频直播电商和短视频电商业务线和项目的运作情况,并对日常及促销活动营销进行数据分析,为业务决策提供数据支持,用数据助力业务增长;\n通过流程量化分析,定位业务关键节点,将数据转化为可落地和说服力的洞察,辅助推进业务决策;\n对电商行业竞品情况进行监控跟踪与分析,深入了解电商行业及核心玩家,对于淘宝/快手/抖音等主流内容电商平台的规则机制进行深入探索,并为业务提供可行性建议和解决方案。\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328921972448436224', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1265154329933783040', 'RecruitPostId': 62144, 'RecruitPostName': '18427-理财通高级交互设计师', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'CDG', 'ProductName': '', 'CategoryName': '设计', 'Responsibility': '负责理财类产品界面及运营活动的交互设计,\n主动发现产品体验问题,持续提升产品的用户体验,\n参与界面规范的制定与实施\n参与团队专业建设与知识传承', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1265154329933783040', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1328921971144007680', 'RecruitPostId': 68768, 'RecruitPostName': '17759-腾讯视频直播带货商品运营', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '产品', 'Responsibility': '熟悉电商各类目体系和商品运营策略,负责直播带货的商品选品组货,做好商品数据分析,优化商品结构,做好价格管理,确保价格力度;\n根据直播销售目标、为不同的直播项目从商品匹配,选品审核,直播上架做整套的沟通和协作;\n按照直播项目补贴合理配备商品数量及sku比例,颜色结构,价格结构,品类结构等,为直播带货的销售提供有竞争力的商品库;\n建立和完善商品数据体系,通过数据分析定期输出商品运营报告;\n建立商品管理系统,实现商品管理制度化、流程化、标准化。\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328921971144007680', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1287921275146084352', 'RecruitPostId': 64395, 'RecruitPostName': '29569-银行核心系统Java开发工程师', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'CDG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '1、负责银行核心系统的设计、开发、测试,保障系统满足合规要求、业务需求,以及安全、可用性、性能、事务性、代码质量等技术维度的要求;\n2、负责支持银行核心系统的日常运营,保证系统稳定高效运行。', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1287921275146084352', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1328921969717944320', 'RecruitPostId': 68767, 'RecruitPostName': '17759-腾讯视频生态电商运营', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '产品', 'Responsibility': '负责腾讯视频直播电商和短视频电商的日常运营,包括电商平台的政策制定、引入机制、主播管理、MCN机构及供应链拓展、电商内容运营分发策略以及与中台共建服务赋能体系等工作,具体如下:\n1)\t负责电商CP准入机制和标准制定,分品类运营策略,与各内容品类中心协同拓展和引入,日常沟通及关系维护;联动各业务部门,结合平台整体运营策略,进行扶持和孵化\n2)\t负责商家/商品标准制定,拓展引入以及针对性的服务体系建设,并制定营销策略\n3)\t负责电商类CP(内容CP、主播、商家)的赋能体系建设,如培训、日常沟通维护等\n4)\t根据平台数据,识别核心商品卖点和用户需求,通过内容引导、激励导向、扶持策略等多种方式提升电商转化效率 \n5)\t负责直播内容中台共建商品合规,内容审核,售后及违规处理等安全体系\n6)\t协助完善内容电商业务的运营机制,包括不限于数据、财务、流量、品控、对外合作、分成等规则,推动平台电商产品持续迭代完善\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328921969717944320', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1306851278617321472', 'RecruitPostId': 66360, 'RecruitPostName': '30361-腾讯文档后台leader', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '\n1、负责文档后台微服务开发和治理;\n2、负责文档后台协同技术规划和落地;\n3、组建和管理研发团队,培养和打造具有凝聚力战斗力的团队\n4、思考和提升研发团队研发效率和质量', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1306851278617321472', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, {'Id': 0, 'PostId': '1306851279921750016', 'RecruitPostId': 66361, 'RecruitPostName': '30361-腾讯文档前端leader', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '1、负责文档前端架构设计和开发\n2、负责腾讯文档多人协同文档冲突处理算法设计和优化\n3、负责钻研各种前沿技术和创新交互,增强用户体验、开拓前端能力边界\n4、组建和管理研发团队,培养和打造具有凝聚力战斗力的团队\n5、思考和提升研发团队研发效率和质量', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1306851279921750016', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}, ......后面省略

2.2 解析数据

取出我们想要的数据:

class HrSpider(scrapy.Spider):
    name = 'hr'
    allowed_domains = ['tencent']
    start_urls = ['https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613034362228&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn']


    def parse(self, response):
        data = json.loads(response.text)
        for job in data['Data']['Posts']:
            print(job)

start一下:

{'Id': 0, 'PostId': '1316995743826845696', 'RecruitPostId': 67328, 'RecruitPostName': '33851-UE4海外项目技术支持工程师', 'CountryName': '中国', 'LocationName': '上海', 'BGName': 'IEG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '参与管理中心与海外项目研发团队的技术相关沟通;\n与执行制作人紧密配合,与海外项目研发团队保持日常沟通,确保项目的技术层面工作健康推进,预警新产生的风险,协调内外部资源提供支持;\n做好海外项目研发团队阶段性提交版本和代码的整理工作,并提取能够横向分享的优秀内容,为各个研发团队提供更加全面的技术/系统/工具参考;\n参与建立管理中心独有的可共享资源库。', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1316995743826845696', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1328887379171221504', 'RecruitPostId': 68744, 'RecruitPostName': '17759-腾讯视频电商招商&供应链建设', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '产品', 'Responsibility': '主要负责公司直播带货BD品牌货品,搭建成熟的供应商体系; \n与商家沟通,商家异议处理,负责品牌的商务谈判和条款,维护好商家合作关系; \n确保直播前样品准备到位,完成相关流程等; \n配合供应相关部门的货品需求 ;\n可以根据商业化规则为节目定制特色商品并开发。\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328887379171221504', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1328921972448436224', 'RecruitPostId': 68769, 'RecruitPostName': '17759-腾讯视频电商数据分析师', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '战略与投资', 'Responsibility': '负责腾讯视频直播电商和短视频电商数据分析工作,具体如下:\n明确电商业务线及项目的业务方向,构建业务指标体系,建立和完善日常业务报告机制;\n及时、准确、完整地汇总腾讯视频直播电商和短视频电商业务线和项目的运作情况,并对日常及促销活动营销进行数据分析,为业务决策提供数据支持,用数据助力业务增长;\n通过流程量化分析,定位业务关键节点,将数据转化为可落地和说服力的洞察,辅助推进业务决策;\n对电商行业竞品情况进行监控跟踪与分析,深入了解电商行业及核心玩家,对于淘宝/快手/抖音等主流内容电商平台的规则机制进行深入探索,并为业务提供可行性建议和解决方案。\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328921972448436224', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1265154329933783040', 'RecruitPostId': 62144, 'RecruitPostName': '18427-理财通高级交互设计师', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'CDG', 'ProductName': '', 'CategoryName': '设计', 'Responsibility': '负责理财类产品界面及运营活动的交互设计,\n主动发现产品体验问题,持续提升产品的用户体验,\n参与界面规范的制定与实施\n参与团队专业建设与知识传承', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1265154329933783040', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1328921971144007680', 'RecruitPostId': 68768, 'RecruitPostName': '17759-腾讯视频直播带货商品运营', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '产品', 'Responsibility': '熟悉电商各类目体系和商品运营策略,负责直播带货的商品选品组货,做好商品数据分析,优化商品结构,做好价格管理,确保价格力度;\n根据直播销售目标、为不同的直播项目从商品匹配,选品审核,直播上架做整套的沟通和协作;\n按照直播项目补贴合理配备商品数量及sku比例,颜色结构,价格结构,品类结构等,为直播带货的销售提供有竞争力的商品库;\n建立和完善商品数据体系,通过数据分析定期输出商品运营报告;\n建立商品管理系统,实现商品管理制度化、流程化、标准化。\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328921971144007680', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1287921275146084352', 'RecruitPostId': 64395, 'RecruitPostName': '29569-银行核心系统Java开发工程师', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'CDG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '1、负责银行核心系统的设计、开发、测试,保障系统满足合规要求、业务需求,以及安全、可用性、性能、事务性、代码质量等技术维度的要求;\n2、负责支持银行核心系统的日常运营,保证系统稳定高效运行。', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1287921275146084352', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1328921969717944320', 'RecruitPostId': 68767, 'RecruitPostName': '17759-腾讯视频生态电商运营', 'CountryName': '中国', 'LocationName': '北京', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '产品', 'Responsibility': '负责腾讯视频直播电商和短视频电商的日常运营,包括电商平台的政策制定、引入机制、主播管理、MCN机构及供应链拓展、电商内容运营分发策略以及与中台共建服务赋能体系等工作,具体如下:\n1)\t负责电商CP准入机制和标准制定,分品类运营策略,与各内容品类中心协同拓展和引入,日常沟通及关系维护;联动各业务部门,结合平台整体运营策略,进行扶持和孵化\n2)\t负责商家/商品标准制定,拓展引入以及针对性的服务体系建设,并制定营销策略\n3)\t负责电商类CP(内容CP、主播、商家)的赋能体系建设,如培训、日常沟通维护等\n4)\t根据平台数据,识别核心商品卖点和用户需求,通过内容引导、激励导向、扶持策略等多种方式提升电商转化效率 \n5)\t负责直播内容中台共建商品合规,内容审核,售后及违规处理等安全体系\n6)\t协助完善内容电商业务的运营机制,包括不限于数据、财务、流量、品控、对外合作、分成等规则,推动平台电商产品持续迭代完善\n', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328921969717944320', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1306851278617321472', 'RecruitPostId': 66360, 'RecruitPostName': '30361-腾讯文档后台leader', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '\n1、负责文档后台微服务开发和治理;\n2、负责文档后台协同技术规划和落地;\n3、组建和管理研发团队,培养和打造具有凝聚力战斗力的团队\n4、思考和提升研发团队研发效率和质量', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1306851278617321472', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1306851279921750016', 'RecruitPostId': 66361, 'RecruitPostName': '30361-腾讯文档前端leader', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '1、负责文档前端架构设计和开发\n2、负责腾讯文档多人协同文档冲突处理算法设计和优化\n3、负责钻研各种前沿技术和创新交互,增强用户体验、开拓前端能力边界\n4、组建和管理研发团队,培养和打造具有凝聚力战斗力的团队\n5、思考和提升研发团队研发效率和质量', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1306851279921750016', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}
{'Id': 0, 'PostId': '1306851279552651264', 'RecruitPostId': 66362, 'RecruitPostName': '30361-腾讯文档客户端leader', 'CountryName': '中国', 'LocationName': '深圳', 'BGName': 'PCG', 'ProductName': '', 'CategoryName': '技术', 'Responsibility': '1、负责文档客户端架构设计和开发\n2、负责文档客户端高性能渲染和离线数据处理开发\n3、负责钻研各种前沿技术和创新交互,增强用户体验\n4、组建和管理研发团队,培养和打造具有凝聚力战斗力的团队\n5、思考和提升研发团队研发效率和质量', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1306851279552651264', 'SourceID': 1, 'IsCollect': False, 'IsValid': True}

下面我们在items里面定义一个job_name = scrapy.Field()
再导入TencentItem类到hr项目里。把tencent文件夹作为根目录比较好导入,具体方法是右键tencent文件夹,选Mark direct as source root
那么在回到hr项目中,from tencent.items import TencentItem
导入之后,在下面实例化,并取出工作岗位数据添加到item中:

import scrapy
import json
from tencent.items import TencentItem
class HrSpider(scrapy.Spider):
    name = 'hr'
    allowed_domains = ['tencent']
    start_urls = ['https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613034362228&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=cn']


    def parse(self, response):
        data = json.loads(response.text)
        for job in data['Data']['Posts']:
            item = TencentItem()
            item['job_name'] = job['RecruitPostName'] # 工作岗位
            print(item)

我们start打印一下:

{'job_name': '33851-UE4海外项目技术支持工程师'}
{'job_name': '17759-腾讯视频电商招商&供应链建设'}
{'job_name': '17759-腾讯视频电商数据分析师'}
{'job_name': '18427-理财通高级交互设计师'}
{'job_name': '17759-腾讯视频直播带货商品运营'}
{'job_name': '29569-银行核心系统Java开发工程师'}
{'job_name': '17759-腾讯视频生态电商运营'}
{'job_name': '30361-腾讯文档后台leader'}
{'job_name': '30361-腾讯文档前端leader'}
{'job_name': '30361-腾讯文档客户端leader'}

第一页的十个岗位就打印出来了。

2.3 翻页处理

下面我们做一下翻页处理,我们这样处理一下。注意看注释:

import scrapy
import json
from tencent.items import TencentItem
class HrSpider(scrapy.Spider):
    name = 'hr'
    allowed_domains = ['tencent']
    # 新键一个url作为模板,{}占位pageIndex的值
    first_url = 'https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613034362228&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
    # str.format()字符串格式化
    start_urls = [first_url.format(1)]

    def parse(self, response):
        data = json.loads(response.text)
        for job in data['Data']['Posts']:
            item = TencentItem()
            item['job_name'] = job['RecruitPostName'] # 工作岗位
            print(item)

        # 翻页
        for page in range(2,3): # 定义翻页范围
            url = self.first_url.format(page) # 产生新的url
            yield scrapy.Request(url) # 生成scrapy.Request对象,并yield给引擎
             # scrapy.Request()这里面的参数是下一页的完整的url
            

start一下:

{'job_name': '33676-供应链营运专员'}
{'job_name': '27141-FPS手游3D环境艺术家(深圳)'}
{'job_name': '27141-FPS手游3D动画设计师(深圳)'}
{'job_name': '15569-UE4知名IP大世界写实赛车手游-高级客户端开发工程师(深圳)'}
{'job_name': '15569-UE4开放世界大作-技术美术(深圳)'}
{'job_name': '15569-UE4知名IP大世界写实赛车手游-后台开发工程师(深圳)'}
{'job_name': '15569-UE4知名IP大世界写实赛车手游-高级3D特效师(深圳)'}
{'job_name': '15569-UE4开放世界大作-Houdini技术美术(深圳)'}
{'job_name': '15569-UE4知名IP大世界写实赛车手游-高级3D载具美术师(深圳)'}
{'job_name': '15569-UE4开放世界大作-引擎研发工程师(图形渲染方向)'}
{'job_name': '15569-UE4开放世界大作-引擎研发工程师(开放大世界生成方向)'}
{'job_name': '15569-UE4开放世界大作-引擎研发工程师(游戏AI方向)'}
{'job_name': '15569-UE4开放世界大作-引擎研发工程师(物理仿真方向)'}
{'job_name': '15569-UE4开放世界大作-技术美术(上海)'}
{'job_name': '15569-UE4开放世界大作-Houdini技术美术(上海)'}
{'job_name': '15569-UE4知名IP大世界写实赛车手游-高级客户端开发工程师(上海)'}
{'job_name': '33851-UE4海外项目技术支持工程师'}
{'job_name': '17759-腾讯视频电商招商&供应链建设'}
{'job_name': '17759-腾讯视频电商数据分析师'}
{'job_name': '18427-理财通高级交互设计师'}

这里面应该是20个职位。

2.4 获取详情页信息

下面,我们尝试获取每个岗位详情页的信息,这里我们只需要爬取“工作职责”就可以了,其他的数据都是一样处理的。
怎么样获取呢?如果我们直接点击工作岗位,页面会跳转到详情页,也会产生一个url。但同样的,如果我们使用这个url发起请求的话,我们也不能得到详情数据,因为数据是加载的。
https://careers.tencent/search.html首页
https://careers.tencent/jobdesc.html?postId=1328946765579165696详情页
我们右键,检查,network,刷新一下:

我们看到刷新后出现两个加载项。第一个是ByPostid,这个我们见过:

工作职责在这里;

事实上每个工作岗位信息有一个Postid,而后面的详情页就是通过这个Postid发起请求的。我们可以获取每一个岗位的Postid就可以了。

下面的代码注意看注释:

import scrapy
import json
from tencent.items import TencentItem
class HrSpider(scrapy.Spider):
    name = 'hr'
    allowed_domains = ['tencent']
    # 新键一个url作为模板,{}占位pageIndex的值
    first_url = 'https://careers.tencent/tencentcareer/api/post/Query?timestamp=1613034362228&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
    # str.format()字符串格式化
    start_urls = [first_url.format(1)]
    # 新建详情页的url地址模板,{}占位“postId=”的值 
    detail_url = 'https://careers.tencent/tencentcareer/api/post/ByPostId?timestamp=1613120342862&postId={}&language=zh-cn'

    def parse(self, response):
        data = json.loads(response.text)
        for job in data['Data']['Posts']:
            item = TencentItem()
            item['job_name'] = job['RecruitPostName']    # 工作岗位
            post_id = job['PostId']
            d_url = self.detail_url.format(post_id)     # 拼接详情页url
            # 向详情页发起请求
            yield scrapy.Request(
                url=d_url,
                callback=self.detail_content,           # 由这个方法来处理,不再是之前的parse里面的逻辑
                meta={'item':item}            # 作用是把我们之前在parse里面定义的item传递进detail_content方法中来
                                 ) 

        # 翻页
        for page in range(2,3): # 定义翻页范围
            url = self.first_url.format(page)     # 产生新的url
            yield scrapy.Request(url,callback=parse)     # 发起新的请求,并使用parse里面的逻辑处理
            # scrapy.Request()这里面的参数是下一页的完整的url

    def detail_content(self,response):
        # 将parse里面定义的item传递进来
        item = response.meta['item']
        # item = response.meta.get('item') 另一种方法
        data = json.loads(response.text)
        print(data)

start一下:

{'Code': 200, 'Data': {'PostId': '1328946693974007808', 'RecruitPostId': 68771, 'RecruitPostName': 'CSIG02-腾讯教育平台产品组长(深圳)', 'LocationId': 1, 'LocationName': '深圳', 'BGId': 29294, 'BGName': 'CSIG', 'OuterPostTypeID': '40003001', 'CategoryName': '产品', 'ProductName': '腾讯云', 'Responsibility': '1.负责腾实学院教学管理平台saas产品的功能策划、交互设计及优化、竞品分析及产品管理工作; \n2.负责用户反馈和数据分析、提炼用户需求、协调开发人员推动项目整体进展; \n3.负责腾实学院平台产品应用场景、解决方案的包装,并规划、制定整体产品市场策略; \n4.负责制定腾实学院教育产品体系的目标和方向;\n5.协助完成腾实学院整体业务目标。', 'Requirement': '1.本科及以上学历,5年以上平台产品设计或管理工作经验,有高校教学实验平台和教育行业相关产品经验者优先;\n2.计算机/大数据相关专业,对云计算、大数据、人工智能等行业背景有一定的了解;\n3.熟练使用Axure、Word、Excel、PPT等工具; \n4.具备优秀的问题分析、解决和逻辑思维能力,能够根据有限或不确定信息做出产品决策;\n5.沟通表达、学习新知识、执行力、解决问题能力强;\n6.有MOOC平台产品管理工作经验优先考虑。', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1328946693974007808', 'SourceID': 1, 'IsCollect': False}}
{'Code': 200, 'Data': {'PostId': '1306414115794722816', 'RecruitPostId': 66279, 'RecruitPostName': '30361-腾讯文档WEB前端工程师(北京)', 'LocationId': 2, 'LocationName': '北京', 'BGId': 29292, 'BGName': 'PCG', 'OuterPostTypeID': '40001001', 'CategoryName': '技术', 'ProductName': '', 'Responsibility': '负责腾讯文档项目前端开发;\n参与腾讯文档项目新品类架构设计和实现;\n钻研前沿技术和创新交互,增强用户体验、开拓前端能力边界;', 'Requirement': '有丰富的前端开发经验,对一到多个专项技术领域有较深入研究;\n熟悉HTML5、CSS3、JavaScript等技术;熟悉前端性能优化技术,熟悉React或Vue前端开发框架;\n有较强的学习能力和解决问题的能力,能迅速掌握业务知识和开发工具,解决面临的技术问题;\n有较强的沟通表达能力;', 'LastUpdateTime': '2021年02月12日', 'PostURL': 'http://careers.tencent/jobdesc.html?postId=1306414115794722816', 'SourceID': 1, 'IsCollect': False}}
。。。省略

我们要的数据在’data’里面的’Responsibility’里。

# 前面的代码省略
    def detail_content(self,response):
        item = response.meta['item']
        data = json.loads(response.text)
        # 这里我们要在items里面定义一个job_duty = scrapy.Field()  # 工作职责
        item['job_duty'] = data['Data']['Responsibility']      
        print(item)

我们要在items里面定义一个job_duty = scrapy.Field()

start一下:

{'job_duty': '1.负责腾实学院教学管理平台saas产品的功能策划、交互设计及优化、竞品分析及产品管理工作; \n'
             '2.负责用户反馈和数据分析、提炼用户需求、协调开发人员推动项目整体进展; \n'
             '3.负责腾实学院平台产品应用场景、解决方案的包装,并规划、制定整体产品市场策略; \n'
             '4.负责制定腾实学院教育产品体系的目标和方向;\n'
             '5.协助完成腾实学院整体业务目标。',
 'job_name': 'CSIG02-腾讯教育平台产品组长(深圳)'}
{'job_duty': '负责心悦俱乐部的产品策划工作,提升用户活跃度、停留时长、留存,包括:\n'
             '心悦指挥官业务优化,建立高玩带萌新的联动生态,提升游戏乐趣和玩家价值;\n'
             '腾讯硬核玩家社区搭建,围绕民间大神,打造富有调性的心悦社区文化;\n'
             '游戏推荐、下载链路的持续优化,帮用户更精准的推荐游戏;\n'
             '游戏外开黑、交友、语音等创新玩法的探索、设计。',
 'job_name': '25925-游戏平台高级产品经理(深圳)'}
{'job_duty': '对于大型能源企业的互联网平台能够输出运营方法论、设计结合腾讯内部能力的运营类产品方案;\n'
             '协同行业应用产品经理运营行业平台类产品;\n'
             '洞察能源企业营销部门对外服务发展趋势,协同行业应用产品经理结合能源领域场景,协同内部资源与外部合作伙伴,设计营销类解决方案,例如电网营销2.0、加油站运营引流、充电桩运营引流;\n'
             '熟练撰写产品PRD。',
 'job_name': '29777-腾讯云能源行业高级产品经理(北京/上海/深圳)'}
 ......省略

总结一下:
scrapy.Request()里面的参数:

  • url 是继续发起请求的url地址
  • callback 是回调函数,其实就是处理整个爬虫项目的逻辑
  • meta 实现不同的解析函数之间的数据传递

有的同学说我们获取详情页的url也可以不用拼串,因为发现这里已经有一个详情页的url:

其实不行的,因为这个url仍然只是发起第一次请求的url,我们需要的数据并不在里面。正确的做法是解析首页内容里获去Postid,然后拼接url。而拼接的url模板是在详情页的network中刷新后加载出的ByPostid加载项中,点击Headers。里面的Request URL: https://careers.tencent/tencentcareer/api/post/ByPostId?timestamp=1613126271771&postId=1328946765579165696&language=zh-cn
就是第一个详情页的url。

3. 古诗词网补充

在上次的古诗文案例里面我们只爬取了古诗文的内容,但是如果我们点击古诗文的题目,回跳转到详情页面

里面有注释,赏析,作者介绍。下面我们补充一下除了内容之外,爬取注释,赏析,作者介绍。

3.1 验证是否在源码中

照例我们先查看内容是否在网页源码当中,右键>查看网页源码,Ctrl+F调出搜索框,输入搜索内容。

发现在源码当中。

3.2 获取详情页地址

我们在古诗文的标题点击就可以获取详情页,那么详情页的url应该藏在诗文标题里面。我们右键诗文标题选择检查,看看源码的内容:

我们看到在b标签里是诗文标题,上一级的a标签里有一个href的值是一个url,点击就进入了详情页。
我们就拿到了详情页的url地址。

3.3 项目补充

我们打开上次的项目来补充一下代码,首先打开items,加上以下内容:

import scrapy

class GswItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    author = scrapy.Field()
    dynasty = scrapy.Field()
    content = scrapy.Field()
    detail_href = scrapy.Field() # 新加详情页的内容
   
    pass

再打开gs文件,添加内容:

        for gsw_div in gsw_divs:
            # 因为b标签只有标题有,所以我们的路径就简略写了。
            title = gsw_div.xpath('.//b/text()').extract_first() # 标题
            source = gsw_div.xpath('.//p[@class="source"]/a/text()').extract() # 作者和朝代
            detail_href = gsw_div.xpath('.//p/a/@href').extract_first() # 取出详情页的url       
            try:
                author = source[0]    # 作者
                dynasty = source[1]   # 朝代
                content_list = gsw_div.xpath('.//div[@class="contson"]//text()').extract()  # 内容
                content = ''.join(content_list).strip()
                item = GswItem()  # 实例化类

                item['title'] = title
                item['author'] = author
                item['dynasty'] = dynasty
                item['content'] = content
                item['detail_href'] = detail_href # 添加详情页的url
               print(item)
            except IndexError:   # 当出现空列表时会报错,我们使用try语句跳过报错
                continue

下面我们找到gsw项目的start文件运行一下:

爬虫开始
{'author': '王禹偁',
 'content': '一郡官闲唯副使,一年冷节是清明。春来春去何时尽,闲恨闲愁触处生。漆燕黄鹂夸舌健,柳花榆荚斗身轻。脱衣换得商山酒,笑把离骚独自倾。',
 'detail_href': 'https://so.gushiwen/shiwenv_328541b3a99d.aspx',
 'dynasty': '〔宋代〕',
 'title': '清明日独酌'}
{'author': '徐君宝妻',
 'content': '汉上繁华,江南人物,尚遗宣政风流。绿窗朱户,十里烂银钩。一旦刀兵齐举,旌旗拥、百万貔貅。长驱入,歌楼舞榭,风卷落花愁。清平三百载,典章文物,扫地俱休。幸此身未北,犹客南州。破鉴徐郎何在?空惆怅、相见无由。从今后,断魂千里,夜夜岳阳楼。',
 'detail_href': 'https://so.gushiwen/shiwenv_2a564b1048aa.aspx',
 'dynasty': '〔宋代〕',
 'title': '满庭芳·汉上繁华'}
{'author': '辛弃疾',
 'content': '吾拟乞归,犬子以田产未置止我,赋此骂之。\n'
            '吾衰矣,须富贵何时?富贵是危机。暂忘设醴抽身去,未曾得米弃官归。穆先生,陶县令,是吾师。待葺个园儿名“佚老”,更作个亭儿名“亦好”,闲饮酒,醉吟诗。千年田换八百主,一人口插几张匙?便休休,更说甚,是和非!',
 'detail_href': 'https://so.gushiwen/shiwenv_06fb74001c43.aspx',
 'dynasty': '〔宋代〕',
 'title': '最高楼·吾衰矣'}
{'author': '姚燧',
 'content': '欲寄君衣君不还,不寄君衣君又寒。寄与不寄间,妾身千万难。',
 'detail_href': 'https://so.gushiwen/shiwenv_fe642e7b3e15.aspx',
 'dynasty': '〔元代〕',
 'title': '凭阑人·寄征衣'}
 后面内容省略

我们拿到了详情页的url,下面传递给引擎来向详情页发起请求。

3.4 发起请求

				item['title'] = title
                item['author'] = author
                item['dynasty'] = dynasty
                item['content'] = content
               #  item['detail_href'] = detail_href
                # 向详情页发起请求
                yield scrapy.Request(url=detail_href,
                                     callback=parse_detail,
                                     meta={'item':item}

                )

3.5 定义解析详情页方法

我们先在items文件里定义译文及注释:

import scrapy

class GswItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    author = scrapy.Field()
    dynasty = scrapy.Field()
    content = scrapy.Field()
   # detail_href = scrapy.Field() # 详情页url
   detail_content = scrapy.Field()  # 译文
    
    pass

下面我们定义方法来解析详情页内容,先查看目标所在的位置:

解析代码,注意看注释:

import scrapy
from gsw.items import GswItem
class GsSpider(scrapy.Spider):
    name = 'gs'
    allowed_domains = ['gushiwen' ]
    start_urls = []
    for i in range(10):
        start_urls.append('https://www.gushiwen/default_{}.aspx'.format(i+1))

    def parse(self, response):

        gsw_divs = response.xpath('//div[@class="left"]/div[@class="sons"]')
        for gsw_div in gsw_divs:

            title = gsw_div.xpath('.//b/text()').extract_first() # 标题
            source = gsw_div.xpath('.//p[@class="source"]/a/text()').extract() # 作者和朝代
            detail_href = gsw_div.xpath('.//p/a/@href').extract_first()
           
            try:
                author = source[0]    # 作者
                dynasty = source[1]   # 朝代
                content_list = gsw_div.xpath('.//div[@class="contson"]//text()').extract()  # 内容
                content = ''.join(content_list).strip()
                item = GswItem()  # 实例化类
         
                item['title'] = title
                item['author'] = author
                item['dynasty'] = dynasty
                item['content'] = content

                # 向详情页发起请求
                yield scrapy.Request(url=detail_href,
                                     callback=self.parse_detail,
                                     meta={'item':item}

                )
         
                yield item

            except IndexError:   # 当出现空列表时会报错,我们使用try语句跳过报错
                continue

        #  下面是处理翻页的代码
        next_href = response.xpath('//a[@id="amore"]/@href').extract_first()
        if next_href:  # 做个非空判断,防止页面翻完后报错
            next_url = response.urljoin(next_href) # 补全url: url如果是完整的就不管了,不是完整的就补全了
            res = scrapy.Request(next_url) # 生成对象
            yield res  # yield给引擎

       def parse_detail(self,response):
        item = response.meta.get('item')
        # 获取译文及注释
        shang = response.xpath('//div[@class="contyishang"]/p/text()').extract()
        content_shang = ''.join(shang).strip() # 处理空格及换行符
   
        item['detail_content'] = content_shang # 加入items

        yield item
        print(item)


我们start一下

爬虫开始
{'author': '李煜',
 'content': '云鬓乱,晚妆残,带恨眉儿远岫攒。斜托香腮春笋嫩,为谁和泪倚阑干?',
 'detail_content': '美人头发蓬乱,晚妆不整齐,因愁带恨的双眉像远山一样聚在一起。白嫩的小手斜托着面颊,靠在栏杆上,也不知在为谁伤心泪流。云鬓:形容女子像乌云一般浓黑、秀美的鬓发。远岫(xiù):远处的山峰。岫,山。攒(cuán):簇聚,凑集。这里是指紧皱双眉。香腮:指美女的腮颊。春笋:春季的竹笋。这里指女子像春笋样纤润尖细的手指。嫩:柔软纤细。和:带,含。阑干:同“栏干”。\u3000\u3000'
                   '这首词写女子春怨相思的情景。开篇二句写发、写面,虽明白但并不传神,是暗写侧描。三句“带恨眉儿”一语点破,不知何恨,不明白但却极传神,是抓住最具特点的细致景象进行正面描写,由一点入,见意更远。“攒”字是“带恨眉儿”的动作,用“远岫”喻之,不但妥切传神而且独具意蕴,“远岫攒”不可解,“眉儿攒”也不可解。此恨之深,此情之切,自可想见。后两句明白如话,直描其景,直问其情,虽问而不需答,“为谁”二字使前面不明朗的词旨一下子明朗起来,把何以成“恨”的不明白一下子明白开来。\u3000\u3000'
                   '这首词字字不及相思,但字字俱关相思,用形态写心情,用远山喻愁情,笔意清新,淡远幽长,画静而情活。正如况周',
 'dynasty': '〔五代〕',
 'title': '捣练子·云鬓乱'}
{'author': '姚合',
 'content': '不自识疏鄙,终年住在城。过门无马迹,满宅是蝉声。带病吟虽苦,休官梦已清。何当学禅观,依止古先生?',
 'detail_content': '译文不知道自己生性疏懒粗鄙,一年到头住在繁华的都城。门前从来没有车马的痕迹,院子里尽是一片蝉的叫声。身上有病痛吟诗虽觉辛苦,不再做官梦境已变得幽清。什么时候去学学禅机参悟,皈依佛家好古修道的先生。注释疏鄙:粗野,俗陋。这里指诗人自己疏懒的性格。过门:登门;上门。休官:辞去官职。禅(chán)观:即禅理、禅道,学佛参禅。禅,梵语“禅那”的省略,意“静思自虐”,“思维修”,为心注一境、正深思虑的意思。观,即观照。古先生:道家对佛的称呼。\u3000\u3000'
                   '此诗当作于从秘书少监之职退下来之后,具体创作时间不详。姚合从一开始做官,就表现得三心二意,且抱着隐居的态度,致仕后更有向禅之心。此诗即表现其禅心,可与另一首禅诗《谢韬光上人赠百龄藤杖》以及其友人的《与无可上人宿万年姚少府宅》一诗相互印证。\u3000\u3000'
                   '姚合极称赏王维的诗,特别追求王诗中的一种“静趣”,此诗就反映了这个倾向。\u3000\u3000'
                   '首两句:“不自识疏鄙,终年住在城。”姚合自称“野性多疏惰”(《闲居遣怀》其八)。一个性格疏懒,习于野性的人,认为不适宜为官临民,这在旁观者看是很清楚的。而自己偏不了解这点,终年住在城里,丝竹乱耳,案牍劳形,求静不得,求闲不能,皆由于自己的“不自识”。本不乐于城市,今终年住在城里,总得自己寻个譬解。古人说,大隐隐于市,因此认为在城市亦算是隐居。“县去帝城远,为官与隐齐。”(《武功县诗》)自己作这样一番解释,是明心迹,也见心安理得了。这儿写身处县城,却透露了心地的静趣。\u3000\u3000'
                   '景况',
 'dynasty': '〔唐代〕',
 'title': '闲居'}
# 后面的省略了。

总结,在其他代码都不变的情况下,我只增加了获取详情页的url的代码,并使用callback再次发起请求,新定义了parse_detail方法来解析详情页面。

4. log补充

2019-01-19 09:50:48 [scrapy.utils.log] INFO: Scrapy 1.5.1 started (bot: tencent)
2019-01-19 09:50:48 [scrapy.utils.log] INFO: Versions: lxml 4.2.5.0, libxml2 2.9.5, cssselect 1.0.3, parsel 1.5.0, w3lib 1.19.0, Twisted 18.9.0, Python 3.6.5 (v3
.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)], pyOpenSSL 18.0.0 (OpenSSL 1.1.0i  14 Aug 2018), cryptography 2.3.1, Platform Windows-10-10.0
.17134-SP0  ### 爬虫scrpay框架依赖的相关模块和平台的信息
2019-01-19 09:50:48 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'tencent', 'NEWSPIDER_MODULE': 'tencent.spiders', 'ROBOTSTXT_OBEY': True, 'SPIDER_MO
DULES': ['tencent.spiders'], 'USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/53
7.36'}  ### 自定义的配置信息哪些被应用了 
2019-01-19 09:50:48 [scrapy.middleware] INFO: Enabled extensions: ### 插件信息
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.logstats.LogStats']
2019-01-19 09:50:48 [scrapy.middleware] INFO: Enabled downloader middlewares: ### 启动的下载器中间件
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2019-01-19 09:50:48 [scrapy.middleware] INFO: Enabled spider middlewares: ### 启动的爬虫中间件
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2019-01-19 09:50:48 [scrapy.middleware] INFO: Enabled item pipelines: ### 启动的管道
['tencent.pipelines.TencentPipeline']
2019-01-19 09:50:48 [scrapy.core.engine] INFO: Spider opened ### 开始爬去数据
2019-01-19 09:50:48 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2019-01-19 09:50:48 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2019-01-19 09:50:51 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://hr.tencent/robots.txt> (referer: None)  ### 抓取robots协议内容
2019-01-19 09:50:51 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://hr.tencent/position.php?&start=#a0> (referer: None)  ### start_url发起请求
2019-01-19 09:50:51 [scrapy.spidermiddlewares.offsite] DEBUG: Filtered offsite request to 'hr.tencent': <GET https://hr.tencent/position.php?&start=>  ### 提示错误,爬虫中通过yeid交给引擎的请求会经过爬虫中间件,由于请求的url超出allowed_domain的范围,被offsitmiddleware 拦截了
2019-01-19 09:50:51 [scrapy.core.engine] INFO: Closing spider (finished) ### 爬虫关闭
2019-01-19 09:50:51 [scrapy.statscollectors] INFO: Dumping Scrapy stats: ### 本次爬虫的信息统计
{'downloader/request_bytes': 630,
 'downloader/request_count': 2,
 'downloader/request_method_count/GET': 2,
 'downloader/response_bytes': 4469,
 'downloader/response_count': 2,
 'downloader/response_status_count/200': 2,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2019, 1, 19, 1, 50, 51, 558634),
 'log_count/DEBUG': 4,
 'log_count/INFO': 7,
 'offsite/domains': 1,
 'offsite/filtered': 12,
 'request_depth_max': 1,
 'response_received_count': 2,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 1,
 'scheduler/enqueued/memory': 1,
 'start_time': datetime.datetime(2019, 1, 19, 1, 50, 48, 628465)}
2019-01-19 09:50:51 [scrapy.core.engine] INFO: Spider closed (finished)

5. scrapyshell讲解

scrapy为开发者提供了一个不用启动项目就可以测试的终端叫scrapyshell,就像python shell一样,很方便使用来测试代码。
我们尝试在命令交互窗口输入以下命令回车:

scrapy shell https://www.baidu/

结果:


C:\Users\MI>scrapy shell https://www.baidu/
2021-02-15 17:12:02 [scrapy.utils.log] INFO: Scrapy 2.4.1 started (bot: scrapybot)
2021-02-15 17:12:02 [scrapy.utils.log] INFO: Versions: lxml 4.6.2.0, libxml2 2.9.5, cssselect 1.1.0, parsel 1.6.0, w3lib 1.22.0, Twisted 20.3.0, Python 3.8.6rc1 (tags/v3.8.6rc1:08bd63d, Sep  7 2020, 23:10:23) [MSC v.1927 64 bit (AMD64)], pyOpenSSL 20.0.1 (OpenSSL 1.1.1i  8 Dec 2020), cryptography 3.3.1, Platform Windows-10-10.0.18362-SP0
2021-02-15 17:12:02 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.selectreactor.SelectReactor
2021-02-15 17:12:02 [scrapy.crawler] INFO: Overridden settings:
{'DUPEFILTER_CLASS': 'scrapy.dupefilters.BaseDupeFilter',
 'LOGSTATS_INTERVAL': 0}
2021-02-15 17:12:02 [scrapy.extensions.telnet] INFO: Telnet Password: 4479466836444279
2021-02-15 17:12:02 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole']
2021-02-15 17:12:03 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2021-02-15 17:12:03 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2021-02-15 17:12:03 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2021-02-15 17:12:03 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2021-02-15 17:12:03 [scrapy.core.engine] INFO: Spider opened
2021-02-15 17:12:06 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.baidu/> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x0000020CDC3EC250>
[s]   item       {}
[s]   request    <GET https://www.baidu/>
[s]   response   <200 https://www.baidu/>
[s]   settings   <scrapy.settings.Settings object at 0x0000020CDC3EC400>
[s]   spider     <DefaultSpider 'default' at 0x20cdc8d65e0>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
>>>

我们看到最后几行熟悉的内容,其中有一个response , response <200 https://www.baidu/>。这个说明我们请求的网址正确响应了。那么我们可以再输入

response.body

回车

>>> response.body
b'<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>\xe7\x99\xbe\xe5\xba\xa6\xe4\xb8\x80\xe4\xb8\x8b\xef\xbc\x8c\xe4\xbd\xa0\xe5\xb0\xb1\xe7\x9f\xa5\xe9\x81\x93</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=\xe7\x99\xbe\xe5\xba\xa6\xe4\xb8\x80\xe4\xb8\x8b class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu name=tj_trnews class=mnav>\xe6\x96\xb0\xe9\x97\xbb</a> <a href=https://www.hao123 name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu name=tj_trmap class=mnav>\xe5\x9c\xb0\xe5\x9b\xbe</a> <a href=http://v.baidu name=tj_trvideo class=mnav>\xe8\xa7\x86\xe9\xa2\x91</a> <a href=http://tieba.baidu name=tj_trtieba class=mnav>\xe8\xb4\xb4\xe5\x90\xa7</a> <noscript> <a href=http://www.baidu/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu%2f%3fbdorz_come%3d1 name=tj_login class=lb>\xe7\x99\xbb\xe5\xbd\x95</a> </noscript> <script>document.write(\'<a href="http://www.baidu/bdorz/login.gif?login&tpl=mn&u=\'+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ \'" name="tj_login" class="lb">\xe7\x99\xbb\xe5\xbd\x95</a>\');\r\n                </script> <a href=//www.baidu/more/ name=tj_briicon class=bri style="display: block;">\xe6\x9b\xb4\xe5\xa4\x9a\xe4\xba\xa7\xe5\x93\x81</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu>\xe5\x85\xb3\xe4\xba\x8e\xe7\x99\xbe\xe5\xba\xa6</a> <a href=http://ir.baidu>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu/duty/>\xe4\xbd\xbf\xe7\x94\xa8\xe7\x99\xbe\xe5\xba\xa6\xe5\x89\x8d\xe5\xbf\x85\xe8\xaf\xbb</a>&nbsp; <a href=http://jianyi.baidu/ class=cp-feedback>\xe6\x84\x8f\xe8\xa7\x81\xe5\x8f\x8d\xe9\xa6\x88</a>&nbsp;\xe4\xba\xacICP\xe8\xaf\x81030173\xe5\x8f\xb7&nbsp; <img src=//www.baidu/img/gs.gif> </p> </div> </div> </div> </body> </html>\r\n'
>>>

response.body得到的是字节流,也可以response.text这样得到的是字符串。

>>> response.text
'<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123 name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write(\'<a href="http://www.baidu/bdorz/login.gif?login&tpl=mn&u=\'+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ \'" name="tj_login" class="lb">登录</a>\');\r\n                </script> <a href=//www.baidu/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu>关 于百度</a> <a href=http://ir.baidu>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu/img/gs.gif> </p> </div> </div> </div> </body> </html>\r\n'
>>>

也可以查看网页的编码方式:response.encoding

>>> response.encoding
'utf-8'
>>>

比如我们刚解析过的gsw案例,我们可以用srapyshell做简单的解析,里面有xpath工具。

C:\Users\MI>scrapy shell https://so.gushiwen/shiwenv_d61bf6c27218.aspx
# 前面部分省略

[]
2021-02-15 17:27:57 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2021-02-15 17:27:57 [scrapy.core.engine] INFO: Spider opened
2021-02-15 17:28:20 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://so.gushiwen/shiwenv_d61bf6c27218.aspx> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x000001A43BED9250>
[s]   item       {}
[s]   request    <GET https://so.gushiwen/shiwenv_d61bf6c27218.aspx>
[s]   response   <200 https://so.gushiwen/shiwenv_d61bf6c27218.aspx>
[s]   settings   <scrapy.settings.Settings object at 0x000001A43BED92E0>
[s]   spider     <DefaultSpider 'default' at 0x1a43c3d0b50>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser

输入路径,结果:

>>> response.xpath('//div[@class="left"]/div[@class="sons"]')
[<Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div class="sons" id="sonsyuanwen">\n<...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="fanyi1845" class="sons" styl...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="fanyiquan1845" class="sons" ...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxi2751" class="sons" st...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxiquan2751" class="sons...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxi2752" class="sons" st...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxiquan2752" class="sons...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div class="sons">\n<div class="contyi...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxi24175" class="sons" s...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxiquan24175" class="son...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxi24174" class="sons" s...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div id="shangxiquan24174" class="son...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div class="sons">\n<div class="cont">...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div class="sons">\n<div class="cont">...'>, <Selector xpath='//div[@class="left"]/div[@class="sons"]' data='<div class="sons">\n<div class="cont">...'>]

得到的是对象。

6. settings补充

主要说明如何操作里面的值。settings里面的设置值是为了方便各个模块设置而统一集中到一起的。如果有模块需要用其中的设置,直接去引用就可以了。
(后面待续)

7. 总结




7.1 scrapy项目结构

7.1.1 pipelines

接受items返回的数据并处理:

def process_item(self,item,spider):
	pass
	return item

7.1.2 items

提前定义好要爬取的数据:

name = scrapy.Field()

它是一个字典的格式,父类是dict。
注意,如果在爬虫文件中引入了itmes,在pipelines中保存数据的时候要做一个数据格式的转换处理。

7.1.3 settings

它存放的是一些配置文件,一般用大写字母来命名。
常用设置比如:robot协议
LOG_LEVEL
pipelinese的打开
headers添加urser-agent等设置。

7.1.4 spider

name = “爬虫名”
allowed_doumains = [允许爬取的范围] 可以添加多个范围,也可以给一个最大的范围。
start_url = [‘开始的url’] 这个可以修改。

def parse(self,response)
	pass

更多推荐

爬虫(20)Scrapy知识补充+腾讯招聘案例+古诗文详情页+总结