Scrapy分布式爬虫打造搜索引擎——(二) scrapy 爬取伯乐在线

1.开发环境准备

  •  1.爬取策略

    • 目标:爬取“伯乐在线”的所有文章
    • 策略选择:由于“伯乐在线”提供了全部文章的索引页 ,所有不需要考虑url的去重方法,直接在索引页开始,一篇文章一篇文章地进行爬取,一直进行到最后一页即可。
    • 索引页地址:http://blog.jobbole.com/all-posts/
  • 2. 搭建python3虚拟环境

    • 打开cmd,进入命令行,输入workon,查看当前存在的虚拟环境:  workon
    • 为爬虫项目,新建python3虚拟环境: mkvirtualenv -p python3 ArticleSpider_Env
    • 成功新建python3虚拟环境后,输入: workon ,可以看到现在虚拟环境 ArticleSpider_Env 已存在
      • PS C:\Users\GoFree> workon
        
        Pass a name to activate one of the following virtualenvs:
        ==============================================================================
        ArticleSpider_Env
        env_python2.7
        env_python3.6
        PycharmProjects
        PS C:\Users\GoFree>
  • 3.在虚拟环境中,安装scrapy

    • 进入ArticleSpider_Env 虚拟环境,输入 :C:\Users\GoFree>workon ArticleSpider_Env
      • 显示信息如下:
        C:\Users\GoFree>workon ArticleSpider_Env
        (ArticleSpider_Env) C:\Users\GoFree>
    • 安装scrapy包,输入:  pip install scrapy --upgrade
      • 部分安装成功的信息如下:
        Installing collected packages: attrs, pyasn1, pyasn1-modules, six, idna, asn1crypto, pycparser, cffi, cryptography, pyOpenSSL, service-identity, w3lib, lxml, cssselect, parsel, queuelib, PyDispatcher, zope.interface, constantly, incremental, Automat, hyperlink, Twisted, scrapy
        Successfully installed Automat-0.6.0 PyDispatcher-2.0.5 Twisted-18.4.0 asn1crypto-0.24.0 attrs-18.1.0 cffi-1.11.5 constantly-15.1.0 cryptography-2.2.2 cssselect-1.0.3 hyperlink-18.0.0 idna-2.7 incremental-17.5.0 lxml-4.2.1 parsel-1.4.0 pyOpenSSL-18.0.0 pyasn1-0.4.3 pyasn1-modules-0.2.1 pycparser-2.18 queuelib-1.5.0 scrapy-1.5.0 service-identity-17.0.0 six-1.11.0 w3lib-1.19.0 zope.interface-4.5.0
        
        (ArticleSpider_Env) C:\Users\GoFree> 
  • 4.在虚拟环境中,在指定位置创建scrapy项目

    • 定位到想要创建项目的文件夹,输入:scrapy startproject ArticleSpider
      • 创建成功,显示信息如下:(输入: dir ,能看到新创建了ArticleSpider文件夹)
        (ArticleSpider_Env) E:\myGit>scrapy startproject ArticleSpider
        New Scrapy project 'ArticleSpider', using template directory 'c:\\users\\gofree\\.virtualenvs\\articlespider_env\\lib\\site-packages\\scrapy\\templates\\project', created in:
            E:\myGit\ArticleSpider
        
        You can start your first spider with:
            cd ArticleSpider
            scrapy genspider example example.com
        
        (ArticleSpider_Env) E:\myGit>dir
         驱动器 E 中的卷是 新加卷
         卷的序列号是 D609-D119
        
         E:\myGit 的目录
        
        2018/06/11  18:59    <DIR>          .
        2018/06/11  18:59    <DIR>          ..
        2018/06/11  18:59    <DIR>          ArticleSpider
        2018/06/05  20:28    <DIR>          ArticleSpider_origion
        2018/06/08  18:46    <DIR>          machine-learning-lxr
        2018/06/08  22:48    <DIR>          Search-Engine-Implementation-Using-Python
                       0 个文件              0 字节
                       6 个目录 197,621,055,488 可用字节
        
        (ArticleSpider_Env) E:\myGit>
  • 5.使用PyCharm打开新建项目

    • 打开PyCharm,在顶部标签栏File -> open ,选择新建文件夹ArticleSpider 打开,选择在新窗口打开
    • 选择 File -> Settings ,为本项目添加前面创建的虚拟环境。选择如下图所示:(注意,解释器需要选择 ArticleSpider_Env\Scripts\python.py 文件)
  • 6.初始化爬取“伯乐在线”文章页 http://blog.jobbole.com/ 的爬虫文件

    • cmd进入 ArticleSpider 文件夹内 :对我来说,输入: cd ArticleSpider ,就进入了项目文件夹,具体信息如下:
      • (ArticleSpider_Env) E:\myGit>cd ArticleSpider
        
        (ArticleSpider_Env) E:\myGit\ArticleSpider>
    • cmd中输入: scrapy genspider jobbole blog.jobbole.com ,详细信息如下:
      • (ArticleSpider_Env) E:\myGit\ArticleSpider>scrapy genspider jobbole blog.jobbole.com
        Created spider 'jobbole' using template 'basic' in module:
          ArticleSpider.spiders.jobbole

        (ArticleSpider_Env) E:\myGit\ArticleSpider>
    • 生成 jobbole.py文件,在下图所示的路径中:
  • 7. 启动爬取“伯乐在线”的爬虫

    • 输入命令: scrapy crawl jobbole ,出现错误信息如下:
      • ModuleNotFoundError: No module named 'win32api'
      • 根据错误信息的提示,在当前虚拟环境中,安装pypiwin32包
        • 输入:  pip install pypiwin32 ,详细信息如下:
        • (ArticleSpider_Env) E:\myGit\ArticleSpider>pip install pypiwin32
          Collecting pypiwin32
            Downloading https://files.pythonhosted.org/packages/d0/1b/2f292bbd742e369a100c91faa0483172cd91a1a422a6692055ac920946c5/pypiwin32-223-py3-none-any.whl
          Collecting pywin32>=223 (from pypiwin32)
            Downloading https://files.pythonhosted.org/packages/9f/9d/f4b2170e8ff5d825cd4398856fee88f6c70c60bce0aa8411ed17c1e1b21f/pywin32-223-cp36-cp36m-win_amd64.whl (9.0MB)
              100% |████████████████████████████████| 9.0MB 5.9kB/s
          Installing collected packages: pywin32, pypiwin32
          Successfully installed pypiwin32-223 pywin32-223
          
          (ArticleSpider_Env) E:\myGit\ArticleSpider>
  • 8.Pycharm 断点调试

    • Pycharm 断点调试基础

      • 参考:
        • https://www.cnblogs.com/lijunjiang2015/p/7689822.html
        • https://blog.csdn.net/weixin_39198406/article/details/78873120
        • https://blog.csdn.net/u011331731/article/details/72801449
      • 总结如下:
        1. 设置断点:在行号后单击(双击取消)
        2. 两种模式
          • console模式:类似于命令行的出输,可以直观的看到程序每行代码运行的效果。
            • Alt + Shift + F9 运行debug模式
          • Debuger 模式:即断点调试模式
        3. F6:  按顺序往下执行
        4. F7进入
        5. F8跳过。下一步但仅限于设置断点的文件
        6. F9:只在断点和交互处停止,快速调式
        7. F10:显示目前项目所有断点
        8. Shift+F8跳出。当单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函数。
        9. Alt+F9:直接跳到下一个断点
    • 创建main.py文件,调用 jobbole.py,用作调试

      • 在文件夹 ArticleSpider 的根目录下,创建main.py文件。目录结构和main.py的代码分别如下:(注意,execute([])中的字符需放在列表中,连起来就是在cmd中启动爬取jobbole的命令)
        • # -*- coding : utf-8 -*-
          __author__ = 'lxr'
          
          from scrapy.cmdline import execute
          
          import sys
          import os
          
          # os.path.dirname() : 返回传入文件的父目录路径
          # os.path.abspath(__file__) : 返回当前文件的路径
          sys.path.append(os.path.dirname(os.path.abspath(__file__)))
          execute(["scrapy","crawl","jobbole"])
    • 将 ROBOTSTXT_OBEY 设置为False

      • Robots 协议作用:将过滤不符合robots协议的URL。
      • 在后续开发过程中,需要将ROBOTSTXT_OBEY 设置为False。否则,在开启爬虫时,爬虫会因为URL被过滤掉而早早停掉
      • 协议位置如图,更改setting 文件中的 ROBOTSTXT_OBEY = False:
    • 在jobbole.py文件中打断点。如下图所示:

    • 对main.py 进行debug(红色圈围起来的是debug按钮

2.使用xpath方法爬取页面内容

  • 1.xpath基础

    • 1.简介

      • 1. xpath 使用路径表达式在 xml 和 Html 中进行导航
      • 2. xpath包含标准函数库
      • 3. xpath是一个w3c的标准
    • 2.节点关系

      • 1.父节点
      • 2.子节点
      • 3.同胞节点
      • 4.先辈节点
      • 5.后代节点
    • 3.语法

  • 2.具体爬取前的必要说明

    • 现在,以爬取文章 http://blog.jobbole.com/107275/ 为例进行说明。
    • 1.更换 jobbole.py 中的 start_urls,改为 start_urls = ['http://blog.jobbole.com/107275/']

    • 为了更换说明,现在以爬取文章的标题为例

    • 2.在Firefox浏览器上获取当前文章的xpath路径

      • 打开文章,进入文章页面
      • 按F12, 显示页面代码
      • 点击红圈围住的按钮。作用,如点击标题,可以定位到对应的Html代码处
      • 在对应标题的代码处,点击“右键”,选择复制xpath路径。
        • \
      • 得到xpath路径 :/html/body/div[1]/div[3]/div[1]/div[1]/h1/span
    • 3.获取chrome浏览器的xpath路径

      • 方法与从Firefox类似
      • 得到xpath路径://*[@id="post-107275"]/div[1]/h1
    • 4. 编写 jobbole.py ,用两种xpath 去获取标题

      • 代码截图如下:
      • 运行debug,注意红线圈出的爬取结果
      • 爬取结果
        • Firefox 获取的xpath,未能返回爬取数据,返回为空
        • Chrome获取的xpath,成功爬取了希望的标题数据
      • 原因解释:
        • Firefox 按f12获取的HTML代码是页面生成后显示的代码
        • Chrome 按f12获取的HTML代码就是生成此页面的原始代码
      • 结论:在获取xpath 或下节要介绍的 CSS选择器 时,使用 Chrome 进去获取
    • 5.为什么使用SelectorList作为返回值,而不是直接返回节点类型

      • 返回值类型如同所示
      • 解释:
        • 如果获取的不是节点
        • 或者获取的元素内还嵌套其他的节点,还希望对获取的元素做进一步的xpath 筛选
        • 如果返回节点,就不能进行select筛选
        • 所有scrapy对返回值作了一定封装,让我们可以在嵌套的select筛选。
  • 3.使用scrapy shell 调试

    • 原因:在cmd 命令行下,进行scrapy 调试,速度更快,占用的资源更少
    • 用法:可以把在shell 中调试成功的语句粘贴到Pycharm中
    • 启动方式:
      • 打开cmd
      • 进入虚拟环境: workon ArticleSpider_Env
      • 进入 ArticleSpider 项目中 : (ArticleSpider_Env) E:\myGit\ArticleSpider>
      • 启动 scrapy shell 调试: scrapy shell http://blog.jobbole.com/107275/ ,后面跟的URL就是打算爬取的页面地址
      • 开启成功,显示信息如下:
        (ArticleSpider_Env) E:\myGit\ArticleSpider>scrapy shell http://blog.jobbole.com/107275/
        2018-06-12 15:15:53 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: ArticleSpider)
        2018-06-12 15:15:53 [scrapy.utils.log] INFO: Versions: lxml 4.2.1.0, libxml2 2.9.5, cssselect 1.0.3, parsel 1.4.0, w3lib 1.19.0, Twisted 18.4.0, Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 12:30:02) [MSC v.1900 64 bit (AMD64)], pyOpenSSL 18.0.0 (OpenSSL 1.1.0h  27 Mar 2018), cryptography 2.2.2, Platform Windows-10-10.0.17134-SP0
        2018-06-12 15:15:53 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'ArticleSpider', 'DUPEFILTER_CLASS': 'scrapy.dupefilters.BaseDupeFilter', 'LOGSTATS_INTERVAL': 0, 'NEWSPIDER_MODULE': 'ArticleSpider.spiders', 'SPIDER_MODULES': ['ArticleSpider.spiders']}
        2018-06-12 15:15:53 [scrapy.middleware] INFO: Enabled extensions:
        ['scrapy.extensions.corestats.CoreStats',
         'scrapy.extensions.telnet.TelnetConsole']
        2018-06-12 15:15:54 [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']
        2018-06-12 15:15:54 [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']
        2018-06-12 15:15:54 [scrapy.middleware] INFO: Enabled item pipelines:
        []
        2018-06-12 15:15:54 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6026
        2018-06-12 15:15:54 [scrapy.core.engine] INFO: Spider opened
        2018-06-12 15:15:54 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://blog.jobbole.com/107275/> (referer: None)
        [s] Available Scrapy objects:
        [s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
        [s]   crawler    <scrapy.crawler.Crawler object at 0x0000020E3ED38FD0>
        [s]   item       {}
        [s]   request    <GET http://blog.jobbole.com/107275/>
        [s]   response   <200 http://blog.jobbole.com/107275/>
        [s]   settings   <scrapy.settings.Settings object at 0x0000020E41439898>
        [s]   spider     <JobboleSpider 'jobbole' at 0x20e416e29e8>
        [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
        >>>
  • 4.爬取文章的具体信息

    • 注意,在进行具体爬取时,xpath路径应该根据页面的html结构计算得出。不要直接在chrome上选择复制xpath,chrome生成的xpath是根据当前URL生成的,放在其他页面将不能使用。
    • 1.爬取标题

      • extract() : 把 selector 对象 转换为数组(注意观察:想要获取的文本在数组中的第一个位置,通常为extract()[0])
      • 注意,在xpath 地址最后,添加 /text() :表示只获取文本,不需要前后的HTML标签
      • 获取标题的代码如下:
        • title = response.xpath('//div[@class="entry-header"]/h1/text()').extract()[0]
    • 2.爬取发表日期

      • strip():可以除去一段字符串中的空格、回车、换行。
      • replace("想替换的字符1",“用来替换的字符2”):把一段字符串中的1字符用2字符替换
      • 代码: create_date = response.xpath('//div[@class="entry-meta"]/p/text()').extract()[0].strip().replace("·","").strip() 
    • 3.爬取点赞数

      • 代码: praise_nums = response.xpath('//div[@class="post-adds"]/span[1]/h10/text()').extract()[0] ,获得结果为: '1'
      • 考虑为空的情况,即没有,需要使用正则表达式和if-else结果
      • 所以,使用正则表达式:导入 re
        • result = re.match("正则表达式",“匹配文本”)
        • result.group():对应方法查看 https://www.cnblogs.com/lxr1995/p/9148794.html
      • 考虑结果为空,使用if-else语句手动赋值点赞数为0
      • 考虑有匹配的‘1’是字符串,不是数字,使用 int() 进行强制类型转换
      • contains用法: span[contains(@class,"vote-post-up")] :表示取class名包含“vote-post-up”的span元素
      • 代码如下:
        praise_nums = response.xpath('//div[@class="post-adds"]/span[1]/h10/text()').extract()[0]
        match_re = re.match('.*?(\d+).*', praise_nums)
        if match_re:
            praise_nums = int(match_re.group(1))
        else:
            praise_nums = 0
    • 4.爬取收藏数

      • 和点赞数的情况类似,使用正式表达式和if-else语句
      • 代码如下:
        • fav_nums = response.xpath('//div[@class="post-adds"]/span[2]/text()').extract()[0]
          match_re = re.match('.*?(\d+).*',fav_nums)
          if match_re:
              fav_nums = int(match_re.group(1))
          else:
              fav_nums = 0
    • 5.爬取评论数

      • 使用结构可与点赞数、收藏数类比
      • 代码如下:
        • comment_nums = response.xpath('//div[@class="post-adds"]/a/span/text()').extract()[0]
          match_re = re.match('.*?(\d+).*', comment_nums)
          if match_re:
              comment_nums = int(match_re.group(1))
          else:
              comment_nums = 0
    • 6.爬取正文

      • 由于不同网站正文的排版是不同的,所有正文元素分析是一个比较复杂的内容,这里暂时把所有的html元素都保存,以后需要做进一步提取或者样式分析时可以使用
      • 代码如下:
        •  content = response.xpath('//div[@class="entry"]').extract()[0] 
    • 7.爬取标签

      • 返回的是数组,需要将数组中的值连接起来,生成一个标签字符串,使用 '',".join()
      • 备忘:lamda表达式:tag_list = [elem for elem in tag_list if not elem.strip().endwith("评论")]
        • 作用:删除列表找以“评论”结尾的项
      • 代码如下:
        • tag_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract()
          tag = ",".join(tag_list) 
    • 8.总结

      • 在jobboler.py中的添加的代码如下:
        • # -*- coding: utf-8 -*-
          import scrapy
          import re
          
          class JobboleSpider(scrapy.Spider):
              name = 'jobbole'
              allowed_domains = ['blog.jobbole.com']
              start_urls = ['http://blog.jobbole.com/107275/']
              # start_urls = ['http://blog.jobbole.com/114107/']
          
          
          
              def parse(self, response):
          
                  #使用xpath
                  #标题
                  title = response.xpath('//div[@class="entry-header"]/h1/text()').extract()[0]
                  #发表日期
                  create_date = response.xpath('//div[@class="entry-meta"]/p/text()').extract()[0].strip().replace("·","").strip()
                  #点赞数
                  praise_nums = response.xpath('//div[@class="post-adds"]/span[1]/h10/text()').extract()[0]
                  match_re = re.match('.*?(\d+).*', praise_nums)
                  if match_re:
                      praise_nums = int(match_re.group(1))
                  else:
                      praise_nums = 0
                  #收藏数
                  fav_nums = response.xpath('//div[@class="post-adds"]/span[2]/text()').extract()[0]
                  match_re = re.match('.*?(\d+).*',fav_nums)
                  if match_re:
                      fav_nums = int(match_re.group(1))
                  else:
                      fav_nums = 0
                  #评论数
                  comment_nums = response.xpath('//div[@class="post-adds"]/a/span/text()').extract()[0]
                  match_re = re.match('.*?(\d+).*', comment_nums)
                  if match_re:
                      comment_nums = int(match_re.group(1))
                  else:
                      comment_nums = 0
                  #正文
                  content = response.xpath('//div[@class="entry"]').extract()[0]
                  #标签  tag_list = [elem for elem in tag_list ] #在tag_list中有不是标签的项时,过滤使用
                  tag_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract()
                  tag = ",".join(tag_list)
                  pass

3.使用CSS选择权爬取页面内容

  •  1.CSS基本语法

  • 2.爬取具体文章

    • 代码样式类似,用CSS选择器地址替换xpath地址
    • response.xpath 替换成 response.css
    • 输出文本:添加伪类选择器 ::text
    • 对含有多个class的标签,取class名唯一的,来代表该标签
    • 具体代码如下:
      • # -*- coding: utf-8 -*-
        import scrapy
        import re
        
        class JobboleSpider(scrapy.Spider):
            name = 'jobbole'
            allowed_domains = ['blog.jobbole.com']
            start_urls = ['http://blog.jobbole.com/107275/']
            # start_urls = ['http://blog.jobbole.com/114107/']
        
        
        
            def parse(self, response):
        
                #使用CSS选择器
                #标题
                title = response.css('.entry-header h1::text').extract()[0]
                #发表日期
                create_date = response.css('.entry-meta-hide-on-mobile ::text').extract()[0].strip().replace("·","").strip()
                #点赞数
                praise_nums = response.css('.vote-post-up h10::text').extract()[0]
                match_re = re.match('.*?(\d+).*', praise_nums)
                if match_re:
                    praise_nums = int(match_re.group(1))
                else:
                    praise_nums = 0
                #收藏数
                fav_nums = response.css('.bookmark-btn::text').extract()[0]
                match_re = re.match('.*?(\d+).*',fav_nums)
                if match_re:
                    fav_nums = int(match_re.group(1))
                else:
                    fav_nums = 0
                #评论数
                comment_nums = response.css('a[href="#article-comment"] span ::text').extract()[0]
                match_re = re.match('.*?(\d+).*', comment_nums)
                if match_re:
                    comment_nums = int(match_re.group(1))
                else:
                    comment_nums = 0
                #正文
                content = response.css('.entry').extract()[0]
                #标签
                tag_list = response.css('.entry-meta-hide-on-mobile a ::text').extract()
                tag = ",".join(tag_list)
                pass
    • extract()[0] 优化:
      • 对于确定使用  extract()[0] 的时候,可以用  extract_first()  替换
      •  extract_first() 取空值时,会返回一个默认值,默认值可在 () 中指定 "" 为空,而不用抛出异常

4.xpath和CSS选择器总结

  哪种方式适合自己就可以选择哪一种方式,两种方法没有高下之分。

5.编写spider爬取伯乐在线的所有文章

  • 1.逻辑梳理

    • 1.获取文章列表页中的文章url,并交给scrapy下载后并进行解析
    • 2.获取下一页的URL并交给scrapy进行下载,下载完成后交给parse
  • 2.获取文章列表页中的文章url,并交给scrapy下载后并进行解析

    • 1. 伪类选择器 ::attr(属性):提取属性的值

    • 2.提取文章列表页的所有文章url

      • 1. 更改开始url 为所有文章列表: start_urls = ['http://blog.jobbole.com/all-posts/']
      • 2. 获取文章列表中的所有url,返回结果是一个数组 :post_urls = response.css('#archive .floated-thumb .post-thumb a::attr(href)').extract()
      • 3. 通过for循环,遍历得到数组中的每个URL,以便做后续处理 :for post_url in post_urls:
    • 3.把提取到的url交给scrapy下载并进行解析

      • 1.导入scrapy的Request方法:from scrapy.http import Request
      • 2.把通过xpath或css选择器提取字段的代码封装成一个parse_detail方法:(我这里使用CSS选择器)
        • def parse_detail(self, response):
                  #提取文章具体字段
          
                  #使用CSS选择器
                  #标题
                  title = response.css('.entry-header h1::text').extract()[0]
                  #发表日期
                  create_date = response.css('.entry-meta-hide-on-mobile ::text').extract()[0].strip().replace("·","").strip()
                  #点赞数
                  praise_nums = response.css('.vote-post-up h10::text').extract()[0]
                  match_re = re.match('.*?(\d+).*', praise_nums)
                  if match_re:
                      praise_nums = int(match_re.group(1))
                  else:
                      praise_nums = 0
                  #收藏数
                  fav_nums = response.css('.bookmark-btn::text').extract()[0]
                  match_re = re.match('.*?(\d+).*',fav_nums)
                  if match_re:
                      fav_nums = int(match_re.group(1))
                  else:
                      fav_nums = 0
                  #评论数
                  comment_nums = response.css('a[href="#article-comment"] span ::text').extract()[0]
                  match_re = re.match('.*?(\d+).*', comment_nums)
                  if match_re:
                      comment_nums = int(match_re.group(1))
                  else:
                      comment_nums = 0
                  #正文
                  content = response.css('.entry').extract()[0]
                  #标签
                  tag_list = response.css('.entry-meta-hide-on-mobile a ::text').extract()
                  tag = ",".join(tag_list)
                  pass
      • 3. 在for 循环内,调用Request(url,callback)
        • url 赋值为 提取到的文章URL
        • callback 赋值 刚封装的parse_detail方法,用来解析具体文章url中的内容
        • 由于parse_detail 在 jobbole类内,使用 self.parse_detail 调用,不需要传参数
        • URL优化:由于传入的post_url 可能为 /107275/ ,所以需要和主域名http://blog.jobbole.com 拼接成完整的URL
          • 方法:导入 from urllib import parse
          • 修改url : parse.urljoin(response,post_url) ,会自动提取response的主域名和post_url的子域名进行拼接
        • 详细代码为:Request(url = parse.urljoin(response.url,post_url),callback = self.parse_detail) 
      • 4. 交给scrapy进行下载:
        • 使用关键字yield ,详细代码:yield Request(url = parse.urljoin(response.url,post_url),callback = self.parse_detail)
  • 3.提取下一页url,交给scrapy进行下载

    • 1. 用两个(多个)类指定同一个类的CSS选择性方法:去除空格即可
      • css(".next.page-numbers)
    • 2. 分析“下一页”的CSS选择器,提取“下一页”URL
      • 具体代码:next_url = response.css(".next.page-numbers ::attr(href)").extract_first("")
    • 3.如果提取到下一页url,就加给scrapy进行处理
      • 使用 if 判断是否取到 下一页url
      • 取到,用yield 传递给 scrapy 进行下载
      • 代码如下:
        • if next_url:
              yield Request(url=parse.urljoin(response.url,next_url), callback=self.parse)
  • 4.完成全部文章爬取,jobbole.py的代码如下:

    • # -*- coding: utf-8 -*-
      import scrapy
      import re
      from scrapy.http import Request
      from urllib import parse
      
      class JobboleSpider(scrapy.Spider):
          name = 'jobbole'
          allowed_domains = ['blog.jobbole.com']
          start_urls = ['http://blog.jobbole.com/all-posts/']
      
      
      
          def parse(self, response):
              """
              1.获取文章列表页中的文章url,并交给scrapy下载后并进行解析
              2.获取下一页的URL并交给scrapy进行下载,下载完成后交给parse
              """
      
              #获取文章列表页中的文章url,并交给scrapy下载后并进行解析
              post_urls = response.css('#archive .floated-thumb .post-thumb a::attr(href)').extract()
              for post_url in post_urls:
                  yield Request(url = parse.urljoin(response.url, post_url),callback = self.parse_detail)
      
              #提取下一页URL,并交给scrapy进行下载
              next_url = response.css(".next.page-numbers ::attr(href)").extract_first("")
              if next_url:
                  yield Request(url=parse.urljoin(response.url,next_url), callback=self.parse)
      
      
      
          def parse_detail(self, response):
              #提取文章具体字段
      
              #使用CSS选择器
              #标题
              title = response.css('.entry-header h1::text').extract()[0]
              #发表日期
              create_date = response.css('.entry-meta-hide-on-mobile ::text').extract()[0].strip().replace("·","").strip()
              #点赞数
              praise_nums = response.css('.vote-post-up h10::text').extract()[0]
              match_re = re.match('.*?(\d+).*', praise_nums)
              if match_re:
                  praise_nums = int(match_re.group(1))
              else:
                  praise_nums = 0
              #收藏数
              fav_nums = response.css('.bookmark-btn::text').extract()[0]
              match_re = re.match('.*?(\d+).*',fav_nums)
              if match_re:
                  fav_nums = int(match_re.group(1))
              else:
                  fav_nums = 0
              #评论数
              comment_nums = response.css('a[href="#article-comment"] span ::text').extract()[0]
              match_re = re.match('.*?(\d+).*', comment_nums)
              if match_re:
                  comment_nums = int(match_re.group(1))
              else:
                  comment_nums = 0
              #正文
              content = response.css('.entry').extract()[0]
              #标签
              tag_list = response.css('.entry-meta-hide-on-mobile a ::text').extract()
              tag = ",".join(tag_list)
      
              # #使用xpath
              # #标题
              # title = response.xpath('//div[@class="entry-header"]/h1/text()').extract()[0]
              # #发表日期
              # create_date = response.xpath('//div[@class="entry-meta"]/p/text()').extract()[0].strip().replace("·","").strip()
              # #点赞数
              # praise_nums = response.xpath('//div[@class="post-adds"]/span[1]/h10/text()').extract()[0]
              # match_re = re.match('.*?(\d+).*', praise_nums)
              # if match_re:
              #     praise_nums = int(match_re.group(1))
              # else:
              #     praise_nums = 0
              # #收藏数
              # fav_nums = response.xpath('//div[@class="post-adds"]/span[2]/text()').extract()[0]
              # match_re = re.match('.*?(\d+).*',fav_nums)
              # if match_re:
              #     fav_nums = int(match_re.group(1))
              # else:
              #     fav_nums = 0
              # #评论数
              # comment_nums = response.xpath('//div[@class="post-adds"]/a/span/text()').extract()[0]
              # match_re = re.match('.*?(\d+).*', comment_nums)
              # if match_re:
              #     comment_nums = int(match_re.group(1))
              # else:
              #     comment_nums = 0
              # #正文
              # content = response.xpath('//div[@class="entry"]').extract()[0]
              # #标签  tag_list = [elem for elem in tag_list ] #在tag_list中有不是标签的项时,过滤使用
              # tag_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract()
              # tag = ",".join(tag_list)

6.items 设计

  • 1.数据爬取的主要目的

    • 从非结构性的数据源提取到结构性的数据
  • 2.提取数据后,如何把数据返回?

    • 最简单的方式:将提取到的字段分别放到字典当中,然后通过字典返回给scrapy。
      • 缺点:字典虽然好用,但是缺少一些结构性的东西,比如:容易打错字段的名字。
      • 解决:为了将这些东西进行完整的格式化,scrapy提供了item类。
  • 3.item简介

    • 作用
      • 类似字典,但是比字典的功能齐全
      • 可以让我们自己指定字段。
    • 运行流程:当我们对item进行实例化,在spider中做yield时,当scrapy发现这是一个item实例,就会直接把这个item路由到pipelines中。
      • 好处:在pipelines中集中处理数据的保存、去重等等操作。
  • 4.补充:爬取所有文章列表页面中,每篇文章的封面

    • 修改爬取本页面所有文章url的方式:
      • 理由:由于需要获取文章封面的url,所以改成先获取文章节点,在通过for循环,分布提取文章url、文章封面url。
    • 添加for循环下的 Request()方法的参数: meta={"front_image_url":image_url}
      • 其中image_url为提取到的封面url,front_image_url为自定义的名称。
      • 通过yield Request() 方式,把image_url 传递到 具体解析文章的方法中并保存。
    • 在具体解析文章的方法parse_detail()中,保存image_url。
      • 代码: front_image_url = response.meta.get("front_image_url","")
      • 其中,传递过来的meta是字典类型
      • 使用get方法,第1个参数是传递过来的图片url的字典名称,第2个参数""是默认参数空,避免取空封面url时抛异常。
  • 5.添加item定义

    • 1.在items.py文件中,定义JobboleArticleItem类,需要基础scrapy的item。
    • 2.在类中,具体定义爬虫的字段,指定为scrapy.Field,表示传递任何参数都可以,一共11个字段
      • 添加原来就有的字段:标题,发布日期,点赞数,收藏数,评论数,标签,正文
      • 添加封面url字段
      • 如果封面图已经在本地保存,添加 本地存储的封面地址字段
      • 添加博客url字段
      • 因为现在的博客url字段时变长的,使用md5等压缩算法,添加 博客url定长字段。
    • 3.现在item.py的代码如下:注意,Field() :  Field 后面 必须加 (),否则无法赋值
      • # -*- 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 ArticlespiderItem(scrapy.Item):
            # define the fields for your item here like:
            # name = scrapy.Field()
            pass
        
        class JobboleArticleItem(scrapy.Item):
            url = scrapy.Field() #博客url
            url_object_id =scrapy.Field() #url经过MD5等压缩成固定长度
            front_image_url = scrapy.Field() # 封面url
            front_image_path = scrapy.Field() # 本地存储的封面路径
        
            title = scrapy.Field()
            create_date = scrapy.Field()
            praise_nums= scrapy.Field()
            fav_nums= scrapy.Field()
            comment_nums= scrapy.Field()
            content= scrapy.Field()
            tag= scrapy.Field()
  • 6.把爬取的值填充到item项中

    • 1.在jobbole.py中导入刚定义的JobboleArticleItem
    • 2.在parse_detail()中实例化JobboleArticleItem对象: article_item = JobboleArticleItem() 
    • 3.以字典方式将解析得到的值传递给item,如:article_item["title"] = title
    • 4.全部赋值后,用 yield item 把 item 传递给 pipeline : yield article_item
      • 完整代码如下:
        article_item = JobboleArticleItem()
        article_item["title"] = title
        article_item["create_date"] = create_date
        article_item["praise_nums"] = praise_nums
        article_item["fav_nums"] = fav_nums
        article_item["comment_nums"] = comment_nums
        article_item["content"] = content
        article_item["tags"] = tags
        
        article_item["url"] = response.url
        article_item["front_image_url"] = front_image_url
        # article_item["url_object_id"] =
        # article_item["front_image_path"] =
        
        yield article_item
    • 5.为了使第4步生效,在setting中,对这三行去注释:
  • 7.完善item

    • 1. 下载图片到本地

      • 1.新建images文件夹,存放图片,位置如图:
      • 2. 修改setting:
        • ITEM_PIPELINES = {
             'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
             'scrapy.pipelines.images.ImagesPipeline':1,
          }
          IMAGES_URLS_FIELD = "front_image_url"
          project_dir = os.path.abspath(os.path.dirname(__file__))
          IMAGES_STORE = os.path.join(project_dir,"images")
        • 解释:'scrapy.pipelines.images.ImagesPipeline':1 :开启图片下载的方法,数字代表优先级,越低优先级越高
        • IMAGES_URLS_FIELD:指向保存封面地址的变量
        • IMAGES_STORE: 指定下载图片的目录
        • 考虑程序会运行在远程,使用相对路径存放图片
          • os.path.abspath():返回当前文件的绝对路径
          • os.path.dirname(): 返回当前文件的文件名
          • os.path.join():连接文件和文件名
      • 3.虚拟环境安装pillow: (ArticleSpider_Env) E:\myGit\ArticleSpider>pip install pillow
      • 4.在运行时,报错,把图片地址转换成数组: article_item["front_image_url"] = [front_image_url]
      • 5.点击”运行“,在images文件夹中生成了图片
    • 2. 完善封面本地存储路径 front_image_path

      • 1.在 pipelines.py 中,添加语句: from scrapy.pipelines.images import ImagesPipeline
      • 2.在 pipelines.py 中,添加  class ArticleImagePipeline(ImagesPipeline): ,该类继承 ImagesPipeline方法
      • 3.在添加的类中,重载  def item_completed(self, results, item, info): 方法
      • 4.在 setting.py中,添加 ArticleImagePipeline ,代码为: 'ArticleSpider.pipelines.ArticleImagePipeline': 1,
        • 设置的优先级高,先处理该方法,赋值item 的图片本地路径,在处理整个item.
      • 5.pipeline.py具体代码如下:
        • # -*- coding: utf-8 -*-
          
          # Define your item pipelines here
          #
          # Don't forget to add your pipeline to the ITEM_PIPELINES setting
          # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
          
          from scrapy.pipelines.images import ImagesPipeline
          
          class ArticlespiderPipeline(object):
              def process_item(self, item, spider):
                  return item
          
          class ArticleImagePipeline(ImagesPipeline):
              def item_completed(self, results, item, info):
                  for ok , value in results:
                      images_file_path = value["path"]
                  item["front_image_path"] = images_file_path
          
                  return item
        • 注意,最后要返回 item.
      • 6.setting.py的具体代码如下:
        • # Configure item pipelines
          # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
          ITEM_PIPELINES = {
             'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
             # 'scrapy.pipelines.images.ImagesPipeline':1,
             'ArticleSpider.pipelines.ArticleImagePipeline': 1,
          }
          IMAGES_URLS_FIELD = "front_image_url"
          project_dir = os.path.abspath(os.path.dirname(__file__))
          IMAGES_STORE = os.path.join(project_dir,"images")
    • 3.完善博客url MD5 压缩定长地址 url_object_id

      • 1. 进行utils 项目文件夹,并在其中新建python 文件 common.py
      • 2. 在 common.py 中
        • 1.导入hashlib ,import hashlib
        • 2.编写 def get_md5(url): ,将 url 经过md5方法压缩
        • 3.common.py代码如下:
        • # -*- coding : utf-8 -*-
          __author__ = "lxr"
          
          import hashlib
          
          def get_md5(url):
              # 判断 url 是否为 unicode ,是,则转换成 utf-8
              if isinstance(url,str): # str代表unicode
                  url = url.encode("utf-8")
          
              m = hashlib.md5()
              m.update(url)
              return m.hexdigest() # 返回 抽取的摘要
          
          if __name__ == "__main__" :
              print(get_md5("http://jobbole.com".encode("utf-8")))
      • 3. 在jobbole.py 中
        • 1.导入 get_md5() : from ArticleSpider.utils.common import get_md5 
        • 2.使用get_md5(),为 item 项中的url md5 压缩值 进行赋值:
          •  article_item["url_object_id"] = get_md5(response.url) # url经过md5压缩 

7.数据表设计和保存item到json文件

  • 1.保存item到json文件

    • 1. jobbole.py 修改 def parse_detail(self, response):,添加日期的格式转换(字符串——>日期), import datetime

      • try:
                    create_date = datetime.datetime.strptime(create_date, "%Y/%m/%d").date()  #将字符串格式的日期转换成日期格式
                except Exception as e:
                    create_date = datetime.datetime.now().date()
                article_item["create_date"] = create_date
    • 2. pipeline.py 添加 item信息存储json的方法

      • # -*- coding: utf-8 -*-
        
        # Define your item pipelines here
        #
        # Don't forget to add your pipeline to the ITEM_PIPELINES setting
        # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
        
        from scrapy.pipelines.images import ImagesPipeline
        from scrapy.exporters import JsonItemExporter
        import codecs
        import json
        
        
        class ArticlespiderPipeline(object):
            def process_item(self, item, spider):
                return item
        
        class JsonWithEncodingPipeline(object):
            #  自定义json文件的导出
            def __init__(self):
                self.file = codecs.open('article.json','w',encoding='utf-8')  # 写方式打开json文件
            def process_item(self, item, spider):
                lines = json.dumps(dict(item), ensure_ascii=False) + "\n"  # item强制转为字典,再解析为json串 ; arcii设置false
                self.file.write(lines)  # json串写入文件
                return item
            def spider_closed(self,spider):
                self.file.close()  #关闭文件
        
        class JsonExporterPipeline(object):
            # 调用scrapy 提供的 json exporter ,导出json文件
            def __init__(self):
                self.file = open('articleexport.json', 'wb') # b二进制
                self.exporter = JsonItemExporter(self.file, encoding='utf-8', ensure_ascii=False)
                self.exporter.start_exporting()  # 开始导出json文件
        
            def close_spider(self, spider):
                self.exporter.finish_exporting()  # 停止导出文件
                self.file.close()  # 关闭文件
        
            def process_item(self, item, spider):
                self.exporter.export_item(item)
                return item
        
        
        class ArticleImagePipeline(ImagesPipeline):
            def item_completed(self, results, item, info):
                for ok , value in results:
                    images_file_path = value["path"]
                item["front_image_path"] = images_file_path
        
                return item
    •  3.setting.py 修改:

      • ITEM_PIPELINES = {
           'ArticleSpider.pipelines.JsonExporterPipeline': 2,
           # 'scrapy.pipelines.images.ImagesPipeline':1,
           'ArticleSpider.pipelines.ArticleImagePipeline': 1,
        }
  • 2.数据库表设计

    • 1. 新建article_spider数据库:

    • 2.新建表 article:

8.通过pipeline保存数据到mysql

  • 1.虚拟环境安装mysql驱动

    • (ArticleSpider_Env) C:\Users\GoFree>pip install mysqlclient
  • 2.使用Twisted框架,实现mysql的异步存取,pipeline.py添加如下代码:

    • import MySQLdb.cursors
      from twisted.enterprise import adbapi
      
      class MysqlTwistedPipeline(object):
      
          def __init__(self, dbpool):
              self.dbpool = dbpool
      
          @classmethod
          def from_settings(cls, settings):  # 将setting.py中的值导入
              dbparms = dict(
                  host = settings["MYSQL_HOST"],
                  db = settings["MYSQL_DBNAME"],
                  user = settings["MYSQL_USER"],
                  passwd = settings["MYSQL_PASSWORD"],
                  charset = "utf8",
                  use_unicode = True,
                  cursorclass = MySQLdb.cursors.DictCursor,
              )
              dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
              return cls(dbpool)
      
          def process_item(self, item, spider):
          #  使用Twisted提供的框架,将mysql插入变成异步执行
              query = self.dbpool.runInteraction(self.do_insert, item)
              query.addErrback(self.handle_error) # 处理异常
      
          def handle_error(self, failure):
              #  处理异步插入的异常
              print(failure)
      
          def do_insert(self, cursor, item):
              # 执行具体操作
              insert_sql = """
                          insert into jobbole_article(title, create_date, url, url_object_id, front_image_url, front_image_path, comment_nums, fav_nums, praise_nums, tags, content)
                          values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                      """
              cursor.execute(insert_sql, (item["title"], item["create_date"], item["url"], item["url_object_id"], item["front_image_url"], item["front_image_path"], item["comment_nums"], item["fav_nums"],item["praise_nums"], item["tags"], item["content"]))
  • 3. 修改setting.py配置:

    • ITEM_PIPELINES = {
         # 'ArticleSpider.pipelines.JsonExporterPipeline': 2,
         # 'scrapy.pipelines.images.ImagesPipeline':1,
         'ArticleSpider.pipelines.ArticleImagePipeline': 1,
         'ArticleSpider.pipelines.MysqlTwistedPipeline': 2,
      }
      
      #  添加mysql的连接参数
      MYSQL_HOST="localhost"
      MYSQL_DBNAME="article_spider"
      MYSQL_USER = "root"
      MYSQL_PASSWORD = "root"

9.scrapy item loader机制

  • 1. 使用item loader 改写 jobbole.py 中 关于 item 的赋值部分

    • jobbole.py代码如下:
    • # -*- coding: utf-8 -*-
      import scrapy
      import re
      from scrapy.http import Request
      from urllib import parse
      from ArticleSpider.items import JobboleArticleItem,ArticleItemLoader
      from ArticleSpider.utils.common import get_md5
      import datetime
      from scrapy.loader import ItemLoader
      
      class JobboleSpider(scrapy.Spider):
          name = 'jobbole'
          allowed_domains = ['blog.jobbole.com']
          start_urls = ['http://blog.jobbole.com/all-posts/']
      
      
          def parse(self, response):
              """
              1.获取文章列表页中的文章url,并交给scrapy下载后并进行解析
              2.获取下一页的URL并交给scrapy进行下载,下载完成后交给parse
              """
      
              #获取文章列表页中的文章url,并交给scrapy下载后并进行解析
              post_nodes = response.css('#archive .floated-thumb .post-thumb a')
              for post_node in post_nodes:
                  image_url = post_node.css("img::attr(src)").extract_first("")
                  post_url = post_node.css("::attr(href)").extract_first("")
                  yield Request(url = parse.urljoin(response.url, post_url),meta={"front_image_url":image_url},callback = self.parse_detail)
      
              #提取下一页URL,并交给scrapy进行下载
              next_url = response.css(".next.page-numbers ::attr(href)").extract_first("")
              if next_url:
                  yield Request(url=parse.urljoin(response.url,next_url), callback=self.parse)
      
      
      
          def parse_detail(self, response):
              # #提取文章具体字段
              # #使用CSS选择器
              #  通过item loader加载 item
              item_loader = ArticleItemLoader(item=JobboleArticleItem(), response=response)
              front_image_url = response.meta.get("front_image_url", "")  # 封面
              item_loader.add_css("title", ".entry-header h1::text")
              item_loader.add_css("create_date", ".entry-meta-hide-on-mobile ::text")
              item_loader.add_value("url", response.url)
              item_loader.add_value("url_object_id", get_md5(response.url))
              item_loader.add_value("front_image_url", [front_image_url])
              item_loader.add_css("praise_nums", ".vote-post-up h10::text")
              item_loader.add_css("fav_nums", ".bookmark-btn::text")
              item_loader.add_css("comment_nums", 'a[href="#article-comment"] span ::text')
              item_loader.add_css("content", ".entry")
              item_loader.add_css("tags", ".entry-meta-hide-on-mobile a ::text")
      
              article_item = item_loader.load_item()
              yield article_item
  • 2.将传入数据的预处理和输出放在item.py中

    • item.py代码如下:
    • # -*- 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
      from  scrapy.loader import ItemLoader
      from scrapy.loader.processors import MapCompose, TakeFirst, Join
      import datetime
      import re
      
      class ArticlespiderItem(scrapy.Item):
          # define the fields for your item here like:
          # name = scrapy.Field()
          pass
      
      def add_jobbole(value):
          return value + "-jobbole"
      
      
      def date_convert(value):
          try:
              create_date = datetime.datetime.strptime(value, "%Y/%m/%d").date()  # 将字符串格式的日期转换成日期格式
          except Exception as e:
              create_date = datetime.datetime.now().date()
          return create_date
      
      
      def get_nums(value):
          match_re = re.match('.*?(\d+).*', value)
          if match_re:
              nums = int(match_re.group(1))
          else:
              nums = 0
          return nums
      
      def remove_comment_tags(value):
          if "评论" in value :
              return ""
          else:
              return value
      
      def return_value(value):
          return value
      
      class ArticleItemLoader(ItemLoader):
          #  自定义Item loader
          default_output_processor = TakeFirst()
      
      
      class JobboleArticleItem(scrapy.Item):
          url = scrapy.Field() #博客url
          url_object_id =scrapy.Field() #url经过MD5等压缩成固定长度
      
          front_image_url = scrapy.Field(
              output_processor=MapCompose(return_value)
          ) # 封面url
          front_image_path = scrapy.Field() # 本地存储的封面路径
      
          title = scrapy.Field(
              # input_processor = MapCompose(add_jobbole)  #  传值的预处理
              #也可使用lamda表达式 MapCompose(lamda x : x + "-jobbole")
          )
          create_date = scrapy.Field(
              input_processor=MapCompose(date_convert),
          )
          praise_nums= scrapy.Field(
              input_processor=MapCompose(get_nums)
          )
          fav_nums= scrapy.Field(
              input_processor=MapCompose(get_nums)
          )
          comment_nums= scrapy.Field(
              input_processor=MapCompose(get_nums)
          )
          content= scrapy.Field(
          )
          tags= scrapy.Field(
              input_processor=MapCompose(remove_comment_tags),
              output_processor = Join(",")
          )
  • 3.完善front_image_url 的异常处理(没有封面),修改 pipeline.py:

    • 修改 class ArticleImagePipeline(ImagesPipeline):
    • class ArticleImagePipeline(ImagesPipeline):
          def item_completed(self, results, item, info):
              if "front_image_url" in item:
                  for ok , value in results:
                      images_file_path = value["path"]
                  item["front_image_path"] = images_file_path
      
              return item

猜你喜欢

转载自www.cnblogs.com/lxr1995/p/9168484.html