一、scrapy框架简介

1. 介绍

Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。

它是爬虫界最知名的框架。就好比web框架中的django Scrapy之所以能实现异步,得益于twisted框架

twisted有事件队列,哪一个事件有活动,就会执行!

整体架构大致如下:

蓝条部分,表示中间件!

Day137 爬虫系列之第4章-scrapy框架 - 图1

可以将SPIDERS,SCHEDULER,DOWNLOADER,ITEM PIPELINE理解为4个人。

它们不直接通讯,都是通过ENGINE来完成通讯的!

  1. '''
  2. Components:
  3. 1、引擎(EGINE)
  4. 引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。
  5. 2、调度器(SCHEDULER)
  6. 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
  7. 3、下载器(DOWLOADER)
  8. 用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的
  9. 4、爬虫(SPIDERS)
  10. SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求
  11. 5、项目管道(ITEM PIPLINES)
  12. 在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
  13. 下载器中间件(Downloader Middlewares)位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,
  14. 你可用该中间件做以下几件事:
  15.   (1) process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website);
  16.   (2) change received response before passing it to a spider;
  17.   (3) send a new Request instead of passing received response to a spider;
  18.   (4) pass response to a spider without fetching a web page;
  19.   (5) silently drop some requests.
  20. 6、爬虫中间件(Spider Middlewares)
  21. 位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
  22. '''

官网链接

在调度器中,可以设置将重复的网址去重。如果爬取失败,需要再次爬取,那么就不能去重。

所以要不要去重,取决于项目需求而定。

如果访问5个url,当有一个url卡住了。那么它就会切换到队列中其他的url。因为它是异步请求,这里就可以高效利用CPU

2. scrapy 的工作流程

之前学习的 爬虫的基本流程

Day137 爬虫系列之第4章-scrapy框架 - 图2

那么 scrapy是如何帮助我们抓取数据的呢?

Day137 爬虫系列之第4章-scrapy框架 - 图3

3. 数据流

Scrapy中的数据流由执行引擎控制,其过程如下:

1.引擎打开一个网站(open adomain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。

2.引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。

3.引擎向调度器请求下一个要爬取的URL。

4.调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。

5.一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。

6.引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。

7.Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。

8.引擎将(Spider返回的)爬取到的Item给ItemPipeline,将(Spider返回的)Request给调度器。

9.(从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。

上面的内容,参考链接:

https://blog.csdn.net/xun527/article/details/78450789

每访问一个url,就会经历上面列举的9个步骤,它是一个反复过程。

如果访问失败,可以再次请求访问!

spiders包含了爬取和解析这2步。

为什么要有中间件?

请求一个网页,有几十个cookie,每一次发送,都得带上cookie,很麻烦。

比如使用代理,需要换IP访问。封锁一个,换一个!

加上代理访问,需要使用中间件!那么无论任何请求,都可以带上cookie。

item就是解析好的数据

2. 安装

Windows平台

windows平台安装比较难搞,它不像linux一样,一条命令就搞定了!很多人学习scrapy,光安装就卡了半天。

为了不让 学习如痴如醉的你放弃scrapy,这里列举出详细的安装过程!

先说明一下安装环境。使用的是windows 10 家庭版,64位操作系统。已经安装好了Python 3.6

搜索cmd,必须使用管理员身份运行

Day137 爬虫系列之第4章-scrapy框架 - 图4

安装 wheel

  1. C:\Windows\system32>pip3 install wheel

安装 lxml

  1. C:\Windows\system32>pip3 install lxml

安装 pyopenssl

  1. C:\Windows\system32>pip3 install pyopenssl

安装 pywin32

打开网址:

  1. https://sourceforge.net/projects/pywin32/files/pywin32/

点击 Build 221

Day137 爬虫系列之第4章-scrapy框架 - 图5

下载pywin32-221.win-amd64-py3.6.exe

Day137 爬虫系列之第4章-scrapy框架 - 图6

直接运行

Day137 爬虫系列之第4章-scrapy框架 - 图7

点击下一步

Day137 爬虫系列之第4章-scrapy框架 - 图8

点击下一步

Day137 爬虫系列之第4章-scrapy框架 - 图9

最后就可以安装完成了!

安装 twisted

打开网页

  1. https://pypi.org/project/Twisted/#files

这里不要下载,为什么呢?看文件名,它只支持Python 2.7。但我的是Python 3.6啊!

Day137 爬虫系列之第4章-scrapy框架 - 图10

打开另外一个网页:

  1. https://github.com/zerodhatech/python-wheels

下载文件 Twisted-17.9.0-cp36-cp36m-win_amd64.whl

Day137 爬虫系列之第4章-scrapy框架 - 图11

点击下载

Day137 爬虫系列之第4章-scrapy框架 - 图12

点击文件属性,复制路径

Day137 爬虫系列之第4章-scrapy框架 - 图13

安装

  1. C:\Windows\system32>pip3 install C:\Users\vr\Downloads\Twisted-17.9.0-cp36-cp36m-win_amd64.whl

安装 scrapy

  1. C:\Windows\system32>pip3 install scrapy

到这里,scrapy就安装完成了!

Linux平台

Linux平台安装比较简单,一条命令就可以搞定了

  1. pip3 install scrapy

3. 命令行工具

1. 查看帮助

  1. scrapy -h
  2. scrapy <command> -h

2. 两种命令

有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要

Global commands

  1. startproject #创建项目
  2. genspider #创建爬虫程序
  3. settings #如果是在项目目录下,则得到的是该项目的配置
  4. runspider #运行一个独立的python文件,不必创建项目
  5. shell #scrapy shell url地址 在交互式调试,如选择器规则正确与否
  6. fetch #独立于程单纯地爬取一个页面,可以拿到请求头
  7. view #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
  8. version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本

Project-only commands

  1. crawl #运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False
  2. check #检测项目中有无语法错误
  3. list #列出项目中所包含的爬虫名
  4. edit #编辑器,一般不用
  5. parse #scrapy parse url地址 --callback 回调函数 #以此可以验证我们的回调函数是否正确
  6. bench #scrapy bentch压力测试

举例:

创建一个爬虫项目DianShang

  1. C:\Users\xiao>e:
  2. E:\>cd E:\python_script\爬\day3
  3. E:\python_script\爬\day3>scrapy startproject DianShang

生成一个爬虫程序。注意:左边的jd表示模块名,右边的jd.com表示要访问的url。

一个项目还可以访问多个url

  1. E:\python_script\爬\day3>cd DianShang
  2. E:\python_script\爬\day3\DianShang>scrapy genspider jd jd.com

注意:项目名和爬虫程序名不能重复!

运行scrapy项目。注意:这里指定的模块名必须和上面一致!

  1. E:\python_script\爬\day3\DianShang>scrapy crawl jd

一个项目,也可以启动多个模块

主要用到命令,就是上面演示的3个!

  1. 1 scrapy startproject DianShang # 创建爬虫项目
  2. 2 scrapy genspider jd jd.com # 生成一个爬虫程序
  3. 3 scrapy crawl jd # 运行scrapy项目

3. 官网链接

https://docs.scrapy.org/en/latest/topics/commands.html

4. 目录结构

  1. project_name/
  2. scrapy.cfg # 项目的主配置信息,用来部署scrapy时使用
  3. project_name/ # 爬虫项目名称
  4. __init__.py
  5. items.py # 数据存储模板,用于结构化数据,如:Django的Model
  6. pipelines.py # 数据持久化
  7. settings.py # 用户级配置文件
  8. spiders/ # 爬虫程序的目录
  9. __init__.py
  10. 爬虫1.py # 爬虫程序1
  11. 爬虫2.py
  12. 爬虫3.py

文件说明:

  • scrapy.cfg 项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
  • items.py 设置数据存储模板,用于结构化数据,如:Django的Model
  • pipelines 数据处理行为,如:一般结构化的数据持久化
  • settings.py 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT=’xxxx’
  • spiders 爬虫目录,如:创建文件,编写爬虫规则

注意:

1、一般创建爬虫文件时,以网站域名命名

2、默认只能在终端执行命令,为了更便捷操作:

  1. #在项目根目录下新建:entrypoint.py
  2. from scrapy.cmdline import execute
  3. execute(['scrapy', 'crawl', 'xiaohua'])

框架基础:spider类,选择器

举例:

上面已经创建好了DianShang项目,修改settings.py,将下面的配置改为False

  1. ROBOTSTXT_OBEY = False

这是配置是否遵循robots协议,一般情况下,是不遵循的。为啥呢?你要真的遵循的话,还怎么爬呀!

涉及到金融,影响国家安全,影响社会和谐稳定…的信息,切莫爬取,否则后果不堪设想!

每次使用命令行运行 scrapy crawl jd 太麻烦了。

在项目根目录新建一个文件 bin.py。注意,它和scrapy.cfg是同级的!

  1. #在项目根目录下新建:bin.py
  2. from scrapy.cmdline import execute
  3. # 最后一个参数是:爬虫程序名
  4. execute(['scrapy', 'crawl', 'jd'])

直接执行bin.py,就可以启动爬虫程序了!

执行之后,会输出一段红色字符串。注意:它不是报错!

也可以关闭掉,修改 bin.py

  1. #在项目根目录下新建:bin.py
  2. from scrapy.cmdline import execute
  3. # 第三个参数是:爬虫程序名
  4. execute(['scrapy', 'crawl', 'jd','--nolog'])

再次执行,就不会输出了!

二、Spider类

Spiders是定义如何抓取某个站点(或一组站点)的类,包括如何执行爬行(即跟随链接)以及如何从其页面中提取结构化数据(即抓取项目)。换句话说,Spiders是您为特定站点(或者在某些情况下,一组站点)爬网和解析页面定义自定义行为的地方。

  1. 1 生成初始的Requests来爬取第一个URLS,并且标识一个回调函数
  2. 第一个请求定义在start_requests()方法内默认从start_urls列表中获得url地址来生成Request请求,
  3. 默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发
  4. 2 在回调函数中,解析response并且返回值
  5. 返回值可以4种:
  6. 包含解析数据的字典
  7. Item对象
  8. 新的Request对象(新的Requests也需要指定一个回调函数)
  9. 或者是可迭代对象(包含ItemsRequest
  10. 3、在回调函数中解析页面内容
  11. 通常使用Scrapy自带的Selectors,但很明显你也可以使用Beutifulsouplxml或其他你爱用啥用啥。
  12. 4、最后,针对返回的Items对象将会被持久化到数据库
  13. 通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline)
  14. 或者导出到不同的文件(通过Feed exportshttps://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)

举例:

打开DianShang项目,修改DianShang—>spiders—>jd.py

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. class JdSpider(scrapy.Spider):
  4. name = 'jd'
  5. allowed_domains = ['jd.com']
  6. start_urls = ['http://jd.com/']
  7. def parse(self, response):
  8. print(response,type(response))

执行bin.py,输出如下:

  1. <200 https://www.jd.com/> <class 'scrapy.http.response.html.HtmlResponse'>

start_requests

使用Ctrl+鼠标左键,点击这一段代码中的Spider,查看源代码

  1. class JdSpider(scrapy.Spider):

查看start_requests方法,看最后2行代码。

  1. def start_requests(self):
  2. cls = self.__class__
  3. if method_is_overridden(cls, Spider, 'make_requests_from_url'):
  4. warnings.warn(
  5. "Spider.make_requests_from_url method is deprecated; it "
  6. "won't be called in future Scrapy releases. Please "
  7. "override Spider.start_requests method instead (see %s.%s)." % (
  8. cls.__module__, cls.__name__
  9. ),
  10. )
  11. for url in self.start_urls:
  12. yield self.make_requests_from_url(url)
  13. else:
  14. for url in self.start_urls:
  15. yield Request(url, dont_filter=True)

它执行了for循环,self.start_urls就是在JdSpider类中定义的start_urls变量,它是一个列表!

如果列表为空,不会执行yield

最后使用生成器返回了一个Request对象

再查看Request源码

  1. class Request(object_ref):
  2. def __init__(self, url, callback=None, method='GET', headers=None, body=None,
  3. cookies=None, meta=None, encoding='utf-8', priority=0,
  4. dont_filter=False, errback=None, flags=None):
  5. self._encoding = encoding # this one has to be set first
  6. self.method = str(method).upper()
  7. self._set_url(url)
  8. self._set_body(body)
  9. assert isinstance(priority, int), "Request priority not an integer: %r" % priority
  10. self.priority = priority
  11. if callback is not None and not callable(callback):
  12. raise TypeError('callback must be a callable, got %s' % type(callback).__name__)
  13. if errback is not None and not callable(errback):
  14. raise TypeError('errback must be a callable, got %s' % type(errback).__name__)
  15. assert callback or not errback, "Cannot use errback without a callback"
  16. self.callback = callback
  17. self.errback = errback
  18. self.cookies = cookies or {}
  19. self.headers = Headers(headers or {}, encoding=encoding)
  20. self.dont_filter = dont_filter
  21. self._meta = dict(meta) if meta else None
  22. self.flags = [] if flags is None else list(flags)

参数解释:

  • url(string) - 此请求的网址
  • callback(callable) - 将使用此请求的响应(一旦下载)作为其第一个参数调用的函数。有关更多信息,请参阅下面的将附加数据传递给回调函数。如果请求没有指定回调,parse()将使用spider的 方法。请注意,如果在处理期间引发异常,则会调用errback。
  • method(string) - 此请求的HTTP方法。默认为’GET’。
  • meta(dict) - 属性的初始值Request.meta。如果给定,在此参数中传递的dict将被浅复制。
  • body(str或unicode) - 请求体。如果unicode传递了a,那么它被编码为 str使用传递的编码(默认为utf-8)。如果 body没有给出,则存储一个空字符串。不管这个参数的类型,存储的最终值将是一个str(不会是unicode或None)。
  • headers(dict) - 这个请求的头。dict值可以是字符串(对于单值标头)或列表(对于多值标头)。如果 None作为值传递,则不会发送HTTP头。
  • cookie(dict或list) - 请求cookie。
  • encoding(string) - 此请求的编码(默认为’utf-8’)。此编码将用于对URL进行百分比编码,并将正文转换为str(如果给定unicode)。
  • priority(int) - 此请求的优先级(默认为0)。调度器使用优先级来定义用于处理请求的顺序。具有较高优先级值的请求将较早执行。允许负值以指示相对低优先级。
  • dont_filter(boolean) - 表示此请求不应由调度程序过滤。当您想要多次执行相同的请求时忽略重复过滤器时使用。小心使用它,或者你会进入爬行循环。默认为False。
  • errback(callable) - 如果在处理请求时引发任何异常,将调用的函数。这包括失败的404 HTTP错误等页面。它接收一个Twisted Failure实例作为第一个参数。有关更多信息,请参阅使用errbacks在请求处理捕获异常
  • flags(list) - 是一个包含属性初始值的 Request.flags列表。如果给定,列表将被浅复制。

关于参数的更多信息,请参考链接:

https://blog.csdn.net/weixin_37947156/article/details/74974208

一般情况下,要自己定义start_requests方法。为什么呢?因为它不一定能够访问目标网站。

比如访问 https://github.com/ 要获取我的个人信息。直接用GET请求,不带任何信息,是获取不到的。必须登录才行!

举例:

进入DianShang项目,修改 DianShang—>spiders—>jd.py,增加start_requests方法

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class JdSpider(scrapy.Spider):
  5. name = 'jd'
  6. allowed_domains = ['jd.com']
  7. # start_urls = ['http://jd.com/'] # 占时没有用了
  8. def start_requests(self):
  9. r1 = Request(url="http://jd.com/")
  10. yield r1 # 务必使用yield 返回
  11. def parse(self, response):
  12. print(response,type(response))

重新执行bin.py,Pycharm输出如下:

  1. <200 https://www.jd.com/> <class 'scrapy.http.response.html.HtmlResponse'>

注意:推荐使用yield返回,这样可以节省内存。它最后还是会调用for循环!

start_requests方法,虽然没有写callback参数,指定回调函数。它默认的回调函数就是parse

start_requests执行之后,并没有发送请求。它只是一个返回一个Request对象,放到一个请求列表中。由twisted进行异步请求!如果得到了响应信息,则调用parse函数!

上面的parse函数,还没有写return。所以它不会走架构图中的7,8步骤

三、选择器

为了解释如何使用选择器,我们将使用Scrapy shell(提供交互式测试)和Scrapy文档服务器中的示例页面,

这是它的HTML代码:

  1. <html>
  2. <head>
  3. <base href='http://example.com/' />
  4. <title>Example website</title>
  5. </head>
  6. <body>
  7. <div id='images'>
  8. <a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
  9. <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
  10. <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
  11. <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
  12. <a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
  13. </div>
  14. </body>
  15. </html>

首先,让我们打开shell:

  1. scrapy shell https://doc.scrapy.org/en/latest/_static/selectors-sample1.html

然后,在shell加载之后,您将获得响应作为response shell变量,并在response.selector属性中附加选择器。

让我们构建一个XPath来选择title标签内的文本:

  1. >>> response.selector.xpath('//title/text()')
  2. [<Selector xpath='//title/text()' data='Example website'>]

使用XPath和CSS查询响应非常常见,响应包括两个便捷快捷方式:response.xpath()和response.css():

  1. >>> response.xpath('//title/text()')
  2. [<Selector xpath='//title/text()' data='Example website'>]
  3. >>> response.css('title::text')
  4. [<Selector xpath='descendant-or-self::title/text()' data='Example website'>]

正如你所看到的,.xpath()并且.css()方法返回一个 SelectorList实例,这是新的选择列表。此API可用于快速选择嵌套数据:

  1. >>> response.css('img').xpath('@src').extract()
  2. ['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']

要实际提取文本数据,必须调用selector .extract() 方法,如下所示:

  1. >>> response.xpath('//title/text()').extract()
  2. ['Example website']

如果只想提取第一个匹配的元素,可以调用选择器 .extract_first()

  1. >>> response.xpath('//div[@id="images"]/a/text()').extract_first()
  2. 'Name: My image 1 '

现在我们将获得基本URL和一些图像链接:

  1. >>> response.xpath('//base/@href').extract()
  2. ['http://example.com/']
  3. >>>
  4. >>> response.css('base::attr(href)').extract()
  5. ['http://example.com/']
  6. >>>
  7. >>> response.xpath('//a[contains(@href, "image")]/@href').extract()
  8. ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
  9. >>>
  10. >>> response.css('a[href*=image]::attr(href)').extract()
  11. ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
  12. >>>
  13. >>> response.xpath('//a[contains(@href, "image")]/img/@src').extract()
  14. ['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']
  15. >>>
  16. >>> response.css('a[href*=image] img::attr(src)').extract()
  17. ['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']
  18. >>>

四、DupeFilter(去重)

默认使用方式:

  1. DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
  2. Request(...,dont_filter=False) ,如果dont_filter=True则告诉Scrapy这个URL不参与去重。

源码解析:

  1. from scrapy.core.scheduler import Scheduler
  2. Scheduler下的enqueue_request方法:self.df.request_seen(request)

自定义去重规则:

  1. from scrapy.dupefilter import RFPDupeFilter,看源码,仿照BaseDupeFilter
  2. #步骤一:在项目目录下自定义去重文件dup.py
  3. class UrlFilter(object):
  4. def __init__(self):
  5. self.visited = set() #或者放到数据库
  6. @classmethod
  7. def from_settings(cls, settings):
  8. return cls()
  9. def request_seen(self, request):
  10. if request.url in self.visited:
  11. return True
  12. self.visited.add(request.url)
  13. def open(self): # can return deferred
  14. pass
  15. def close(self, reason): # can return a deferred
  16. pass
  17. def log(self, request, spider): # log that a request has been filtered
  18. pass

五、Item(项目)

抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。Scrapy蜘蛛可以像Python一样返回提取的数据。虽然方便和熟悉,但P很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多蜘蛛的较大项目中。

为了定义通用输出数据格式,Scrapy提供了Item类。 Item对象是用于收集抓取数据的简单容器。它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。

1. 声明项目

使用简单的类定义语法和Field 对象声明项。这是一个例子:

  1. import scrapy
  2. class Product(scrapy.Item):
  3. name = scrapy.Field()
  4. price = scrapy.Field()
  5. stock = scrapy.Field()
  6. last_updated = scrapy.Field(serializer=str)

注意那些熟悉Django的人会注意到Scrapy Items被宣告类似于Django Models,除了Scrapy Items更简单,因为没有不同字段类型的概念。

2. 项目字段

Field对象用于指定每个字段的元数据。例如,last_updated上面示例中说明的字段的序列化函数。

您可以为每个字段指定任何类型的元数据。Field对象接受的值没有限制。出于同样的原因,没有所有可用元数据键的参考列表。

Field对象中定义的每个键可以由不同的组件使用,只有那些组件知道它。您也可以根据Field自己的需要定义和使用项目中的任何其他键。

Field对象的主要目标是提供一种在一个地方定义所有字段元数据的方法。通常,行为取决于每个字段的那些组件使用某些字段键来配置该行为。

3. 使用项目

以下是使用上面声明的Product项目对项目执行的常见任务的一些示例 。您会注意到API与dict API非常相似。

  1. 创建项目
  2. >>> product = Product(name='Desktop PC', price=1000)
  3. >>> print product
  4. Product(name='Desktop PC', price=1000)
  5. 获取字段值
  6. >>> product['name']
  7. Desktop PC
  8. >>> product.get('name')
  9. Desktop PC
  10. >>> product['price']
  11. 1000
  12. >>> product['last_updated']
  13. Traceback (most recent call last):
  14. ...
  15. KeyError: 'last_updated'
  16. >>> product.get('last_updated', 'not set')
  17. not set
  18. >>> product['lala'] # getting unknown field
  19. Traceback (most recent call last):
  20. ...
  21. KeyError: 'lala'
  22. >>> product.get('lala', 'unknown field')
  23. 'unknown field'
  24. >>> 'name' in product # is name field populated?
  25. True
  26. >>> 'last_updated' in product # is last_updated populated?
  27. False
  28. >>> 'last_updated' in product.fields # is last_updated a declared field?
  29. True
  30. >>> 'lala' in product.fields # is lala a declared field?
  31. False
  32. 设定字段值
  33. >>> product['last_updated'] = 'today'
  34. >>> product['last_updated']
  35. today
  36. >>> product['lala'] = 'test' # setting unknown field
  37. Traceback (most recent call last):
  38. ...
  39. KeyError: 'Product does not support field: lala'
  40. 访问所有填充值
  41. 要访问所有填充值,只需使用典型的dict API
  42. >>> product.keys()
  43. ['price', 'name']
  44. >>> product.items()
  45. [('price', 1000), ('name', 'Desktop PC')]
  46. 其他常见任务
  47. 复制项目:
  48. >>> product2 = Product(product)
  49. >>> print product2
  50. Product(name='Desktop PC', price=1000)
  51. >>> product3 = product2.copy()
  52. >>> print product3
  53. Product(name='Desktop PC', price=1000)
  54. 从项目创建dicts
  55. >>> dict(product) # create a dict from all populated values
  56. {'price': 1000, 'name': 'Desktop PC'}
  57. dicts创建项目:
  58. >>> Product({'name': 'Laptop PC', 'price': 1500})
  59. Product(price=1500, name='Laptop PC')
  60. >>> Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict
  61. Traceback (most recent call last):
  62. ...
  63. KeyError: 'Product does not support field: lala'

4. 扩展项目

您可以通过声明原始Item的子类来扩展Items(以添加更多字段或更改某些字段的某些元数据)。

例如:

  1. class DiscountedProduct(Product):
  2. discount_percent = scrapy.Field(serializer=str)
  3. discount_expiration_date = scrapy.Field()

六、Item PipeLine

在一个项目被蜘蛛抓取之后,它被发送到项目管道,该项目管道通过顺序执行的几个组件处理它。

每个项目管道组件(有时简称为“项目管道”)是一个实现简单方法的Python类。他们收到一个项目并对其执行操作,同时决定该项目是否应该继续通过管道或被丢弃并且不再处理。

项目管道的典型用途是:

  • cleansing HTML data
  • validating scraped data (checking that the items contain certain fields)
  • checking for duplicates (and dropping them)
  • storing the scraped item in a database

1. 编写自己的项目管道

  1. 每个项管道组件都是一个必须实现以下方法的Python类:
  2. process_itemself,项目,蜘蛛)
  3. 为每个项目管道组件调用此方法。process_item()
  4. 必须要么:返回带数据的dict,返回一个Item (或任何后代类)对象,返回Twisted Deferred或引发 DropItem异常。丢弃的项目不再由其他管道组件处理。
  5. 此外,他们还可以实现以下方法:
  6. open_spiderself,蜘蛛)
  7. 打开蜘蛛时会调用此方法。
  8. close_spiderself,蜘蛛)
  9. 当蜘蛛关闭时调用此方法。
  10. from_crawlerclscrawler
  11. 如果存在,则调用此类方法以从a创建管道实例Crawler。它必须返回管道的新实例。Crawler对象提供对所有Scrapy核心组件的访问,
  12. 如设置和信号; 它是管道访问它们并将其功能挂钩到Scrapy的一种方式。

2. 项目管道示例

(1) 价格验证和丢弃物品没有价格

让我们看看下面的假设管道,它调整 price那些不包含增值税(price_excludes_vat属性)的项目的属性,并删除那些不包含价格的项目:

  1. from scrapy.exceptions import DropItem
  2. class PricePipeline(object):
  3. vat_factor = 1.15
  4. def process_item(self, item, spider):
  5. if item['price']:
  6. if item['price_excludes_vat']:
  7. item['price'] = item['price'] * self.vat_factor
  8. return item
  9. else:
  10. raise DropItem("Missing price in %s" % item)

(2) 将项目写入JSON文件

以下管道将所有已删除的项目(来自所有蜘蛛)存储到一个items.jl文件中,每行包含一个以JSON格式序列化的项目:

  1. import json
  2. class JsonWriterPipeline(object):
  3. def open_spider(self, spider):
  4. self.file = open('items.jl', 'w')
  5. def close_spider(self, spider):
  6. self.file.close()
  7. def process_item(self, item, spider):
  8. line = json.dumps(dict(item)) + "\n"
  9. self.file.write(line)
  10. return item

注意JsonWriterPipeline的目的只是介绍如何编写项目管道。如果您确实要将所有已删除的项目存储到JSON文件中,则应使用Feed导出

(3) 将项目写入数据库

在这个例子中,我们将使用pymongo将项目写入MongoDB。MongoDB地址和数据库名称在Scrapy设置中指定; MongoDB集合以item类命名。

这个例子的要点是展示如何使用from_crawler() 方法以及如何正确地清理资源:

  1. import pymongo
  2. class MongoPipeline(object):
  3. collection_name = 'scrapy_items'
  4. def __init__(self, mongo_uri, mongo_db):
  5. self.mongo_uri = mongo_uri
  6. self.mongo_db = mongo_db
  7. @classmethod
  8. def from_crawler(cls, crawler):
  9. return cls(
  10. mongo_uri=crawler.settings.get('MONGO_URI'),
  11. mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
  12. )
  13. def open_spider(self, spider):
  14. self.client = pymongo.MongoClient(self.mongo_uri)
  15. self.db = self.client[self.mongo_db]
  16. def close_spider(self, spider):
  17. self.client.close()
  18. def process_item(self, item, spider):
  19. self.db[self.collection_name].insert_one(dict(item))
  20. return item

(4) 重复过滤

一个过滤器,用于查找重复项目,并删除已处理的项目。假设我们的项目具有唯一ID,但我们的蜘蛛会返回具有相同ID的多个项目:

  1. from scrapy.exceptions import DropItem
  2. class DuplicatesPipeline(object):
  3. def __init__(self):
  4. self.ids_seen = set()
  5. def process_item(self, item, spider):
  6. if item['id'] in self.ids_seen:
  7. raise DropItem("Duplicate item found: %s" % item)
  8. else:
  9. self.ids_seen.add(item['id'])
  10. return item

3. 激活项目管道组件

要激活Item Pipeline组件,必须将其类添加到 ITEM_PIPELINES设置中,如下例所示:

  1. ITEM_PIPELINES = {
  2. 'myproject.pipelines.PricePipeline': 300,
  3. 'myproject.pipelines.JsonWriterPipeline': 800,
  4. }

您在此设置中为类分配的整数值决定了它们运行的顺序:项目从较低值到较高值类进行。习惯上在0-1000范围内定义这些数字。

pymongo操作,请参考链接:

https://www.cnblogs.com/yuanchenqi/articles/9602847.html

本文参考链接:

https://www.cnblogs.com/yuanchenqi/articles/9509793.html

七、爬取亚马逊

新建一个爬虫程序

  1. scrapy genspider amazon amazon.cn

修改 DianShang—>spiders—>amazon.py,打印响应信息

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. def start_requests(self):
  9. r1 = Request(url="http://amazon.cn/")
  10. yield r1
  11. def parse(self, response):
  12. print(response.text)

修改bin.py

  1. #在项目根目录下新建:bin.py
  2. from scrapy.cmdline import execute
  3. # 第三个参数是:爬虫程序名
  4. execute(['scrapy', 'crawl', 'amazon'])

执行bin.py,发现有一个503错误

  1. 2018-10-01 14:47:19 [scrapy.core.engine] DEBUG: Crawled (503) <GET https://www.amazon.cn/> (referer: None)

说明,被亚马逊给拦截了!怎么办呢?加1个请求头

修改 DianShang—>spiders—>amazon.py

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="http://amazon.cn/",headers=self.settings.get('REQUEST_HEADERS'),)
  16. yield r1
  17. def parse(self, response):
  18. print(response.text)

再次运行bin.py,发现拿到了网页html

  1. <!doctype html><html class="a-no-js" data-19ax5a9jf="dingo">
  2. ...
  3. </html>

先来搜索ipone x,跳转页面如下:

Day137 爬虫系列之第4章-scrapy框架 - 图14

在地址栏中的url太长了,它真正的地址应该是

  1. https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x

修改 DianShang—>spiders—>amazon.py,更改url

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),)
  16. yield r1
  17. def parse(self, response):
  18. print(response.text)

修改bin.py,关闭日志

  1. #在项目根目录下新建:bin.py
  2. from scrapy.cmdline import execute
  3. # 第三个参数是:爬虫程序名
  4. execute(['scrapy', 'crawl', 'amazon',"--nolog"])

再次运行bin.py,输出一段html

先来看搜索到ipone x的页面。我们需要爬取 ipone x的名称,价格,配送方式。

但是这个页面并没有配送方式,点击一个具体的商品。

删掉url后面多余参数,地址如下:

  1. https://www.amazon.cn/dp/B075LGPY95

效果如下:

Day137 爬虫系列之第4章-scrapy框架 - 图15

可以发现,这个页面,我们所需要的3个信息全有。那么直接访问这地址获取就可以了!

获取商品详情链接

第一步,应该先获取所有商品的详情链接。

看搜索到ipone x的页面,比如第一个商品,它有很多可以点击的地方。比如图片,标题,价格等等。都可以链接到商品详情页!

这里我只选择 标题部分,来获取商品的详情链接!

先copy Xpath规则

Day137 爬虫系列之第4章-scrapy框架 - 图16

修改 DianShang—>spiders—>amazon.py,使用xpath规则解析

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),)
  16. yield r1
  17. def parse(self, response):
  18. # 商品详细链接
  19. detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract()
  20. print(detail_urls)

执行bin.py,输出如下:

  1. ['<h2 data-attribute="Apple 苹果 手机 iPhone X 银色 64G" data-max-rows="0" class="a-size-base s-inline s-access-title a-text-normal">Apple 苹果 手机 iPhone X 银色 64G</h2>']

商品名都拿到了,获取href属性使用@href就可以了

修改 DianShang—>spiders—>amazon.py

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),)
  16. yield r1
  17. def parse(self, response):
  18. # 商品详细链接
  19. detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract()
  20. print(detail_urls)

执行bin.py,输出如下:

  1. ['https://www.amazon.cn/dp/B075LGPY95']

这只是获取一个商品,获取整页的商品呢?

先来找规律,每一个商品都是一个li标签。并且还有一个id属性为result_数字。

可以发现,id属性的前缀都是result_ 。那么使用xpath的模拟匹配就可以了!

Day137 爬虫系列之第4章-scrapy框架 - 图17

修改 DianShang—>spiders—>amazon.py,使用模糊匹配

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),)
  16. yield r1
  17. def parse(self, response):
  18. # 商品详细链接
  19. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  20. print(detail_urls)

再次执行bin.py,输出了一堆链接,效果如下:

  1. ['https://www.amazon.cn/dp/B075LGPY95', 'https://www.amazon.cn/dp/B0763KX27G', ...]

随便点击一个,就是商品详情链接。那么如何让scrapy去访问这些链接呢?

注意:只要parse函数返回一个Request对象,那么就会放到异步请求列表里面,由twisted发送异步请求!

修改 DianShang—>spiders—>amazon.py,yield Request对象

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  16. headers=self.settings.get('REQUEST_HEADERS'),)
  17. yield r1
  18. def parse(self, response):
  19. # 商品详细链接
  20. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  21. # print(detail_urls)
  22. for url in detail_urls:
  23. yield Request(url=url,
  24. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  25. callback=self.parse_detail, # 回调函数
  26. dont_filter=True # 不去重
  27. )
  28. def parse_detail(self, response): # 获取商品详细信息
  29. print(response.text)

执行bin.py,输出一堆字符串。那些都是商品详情页的信息!

获取商品名

先来获取商品的名字

Day137 爬虫系列之第4章-scrapy框架 - 图18

修改 DianShang—>spiders—>amazon.py,修改parse_detail,增加XPath规则

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  16. headers=self.settings.get('REQUEST_HEADERS'),)
  17. yield r1
  18. def parse(self, response):
  19. # 商品详细链接
  20. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  21. # print(detail_urls)
  22. for url in detail_urls:
  23. yield Request(url=url,
  24. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  25. callback=self.parse_detail, # 回调函数
  26. dont_filter=True # 不去重
  27. )
  28. def parse_detail(self, response): # 获取商品详细信息
  29. # 商品名
  30. name = response.xpath('//*[@id="productTitle"]/text()').extract()
  31. print(name)

执行输出:

  1. \n Apple 苹果 手机 iPhone X 深空灰色 64G\n

发现它有很多换行符,怎么去除呢?使用strip

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  16. headers=self.settings.get('REQUEST_HEADERS'),)
  17. yield r1
  18. def parse(self, response):
  19. # 商品详细链接
  20. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  21. # print(detail_urls)
  22. for url in detail_urls:
  23. yield Request(url=url,
  24. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  25. callback=self.parse_detail, # 回调函数
  26. dont_filter=True # 不去重
  27. )
  28. def parse_detail(self, response): # 获取商品详细信息
  29. # 商品名
  30. name = response.xpath('//*[@id="productTitle"]/text()').extract()
  31. if name:
  32. name = name.strip()
  33. print(name)

执行bin.py,发现输出为空!

修改bin.py,去除关闭日志

  1. #在项目根目录下新建:bin.py
  2. from scrapy.cmdline import execute
  3. # 第三个参数是:爬虫程序名
  4. execute(['scrapy', 'crawl', 'amazon'])

再次执行,发现有一个错误

  1. AttributeError: 'list' object has no attribute 'strip'

哦,原来name是一个list对象。那么使用extract就不合适了,修改为extract_first。匹配第一个结果!

修改 DianShang—>spiders—>amazon.py,修改parse_detail,使用extract_first

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  16. headers=self.settings.get('REQUEST_HEADERS'),)
  17. yield r1
  18. def parse(self, response):
  19. # 商品详细链接
  20. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  21. # print(detail_urls)
  22. for url in detail_urls:
  23. yield Request(url=url,
  24. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  25. callback=self.parse_detail, # 回调函数
  26. dont_filter=True # 不去重
  27. )
  28. def parse_detail(self, response): # 获取商品详细信息
  29. # 商品名,获取第一个结果
  30. name = response.xpath('//*[@id="productTitle"]/text()').extract_first()
  31. if name:
  32. name = name.strip()
  33. print(name)

再次执行,输出:

  1. ...
  2. Apple 苹果 手机 iPhone X 深空灰色 64G
  3. ...

这下就正常了!

获取商品价格

商品价格,和商品名称,也是同样的道理。使用谷歌浏览器直接复制XPath规则,就可以了!

修改 DianShang—>spiders—>amazon.py,修改parse_detail

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  16. headers=self.settings.get('REQUEST_HEADERS'),)
  17. yield r1
  18. def parse(self, response):
  19. # 商品详细链接
  20. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  21. # print(detail_urls)
  22. for url in detail_urls:
  23. yield Request(url=url,
  24. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  25. callback=self.parse_detail, # 回调函数
  26. dont_filter=True # 不去重
  27. )
  28. def parse_detail(self, response): # 获取商品详细信息
  29. # 商品名,获取第一个结果
  30. name = response.xpath('//*[@id="productTitle"]/text()').extract_first()
  31. if name:
  32. name = name.strip()
  33. # 商品价格
  34. price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first()
  35. print(name,price)

执行bin.py,输出如下:

  1. ...
  2. Apple 苹果 手机 iPhone X 深空灰色 64G 7,288.00
  3. ...

获取商品配送方式

使用谷歌浏览器直接复制XPath规则

修改 DianShang—>spiders—>amazon.py,修改parse_detail

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  16. headers=self.settings.get('REQUEST_HEADERS'),)
  17. yield r1
  18. def parse(self, response):
  19. # 商品详细链接
  20. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  21. # print(detail_urls)
  22. for url in detail_urls:
  23. yield Request(url=url,
  24. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  25. callback=self.parse_detail, # 回调函数
  26. dont_filter=True # 不去重
  27. )
  28. def parse_detail(self, response): # 获取商品详细信息
  29. # 商品名,获取第一个结果
  30. name = response.xpath('//*[@id="productTitle"]/text()').extract_first()
  31. if name:
  32. name = name.strip()
  33. # 商品价格
  34. price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first()
  35. # 配送方式
  36. delivery = response.xpath('//*[@id="ddmMerchantMessage"]/a/text()').extract_first()
  37. print(name,price,delivery)

执行bin.py,输出如下:

  1. ...
  2. Apple iPhone X 全网通 移动联通电信4G 热销中 (深空灰, 64G) 7,288.00 新思维官方旗舰店
  3. X-Doria 461238 Apple iPhone X14.73 厘米(5.8 英寸)灰色 173.70 了解更多。
  4. ...

发现了一个问题,如果是手机壳,比如这个链接:

  1. https://www.amazon.cn/dp/B0787HGXQX

使用Xpath获取时,输出: 了解更多

为什么呢?因为我们获取的是a标签的值!

Day137 爬虫系列之第4章-scrapy框架 - 图19

所以获取时,就是了解更多了!看上面,有一个亚马逊中国,这个才是我们想要的!

在span标签id=ddmMerchantMessage,这标签中,它里面包含了2个标签,分别是b标签和a标签。

那么我们取b标签的text()值,就可以了!

修改 DianShang—>spiders—>amazon.py,修改parse_detail

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. class AmazonSpider(scrapy.Spider):
  5. name = 'amazon'
  6. allowed_domains = ['amazon.cn']
  7. # start_urls = ['http://amazon.cn/']
  8. # 自定义配置,注意:变量名必须是custom_settings
  9. custom_settings = {
  10. 'REQUEST_HEADERS': {
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  12. }
  13. }
  14. def start_requests(self):
  15. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  16. headers=self.settings.get('REQUEST_HEADERS'),)
  17. yield r1
  18. def parse(self, response):
  19. # 商品详细链接
  20. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  21. # print(detail_urls)
  22. for url in detail_urls:
  23. yield Request(url=url,
  24. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  25. callback=self.parse_detail, # 回调函数
  26. dont_filter=True # 不去重
  27. )
  28. def parse_detail(self, response): # 获取商品详细信息
  29. # 商品名,获取第一个结果
  30. name = response.xpath('//*[@id="productTitle"]/text()').extract_first()
  31. if name:
  32. name = name.strip()
  33. # 商品价格
  34. price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first()
  35. # 配送方式,*[1]表示取第一个标签,也就是b标签
  36. delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first()
  37. print(name,price,delivery)

执行bin.py,输出如下:

  1. ...
  2. hayder iphone X 保护套透明 TPU 超薄电镀防护防撞手机壳适用于苹果 iPhone X 10 149.09 亚马逊美国
  3. Apple iPhone X 移动联通电信4G手机 (256GB, 银色) 8,448.00 新思维官方旗舰店
  4. ...

发现手机壳的配送方式,也是正确的!

保存到MongoDB

Item

要想将数据保存到MongoDB中,需要使用item

修改 DianShang—>items.py,默认的函数名可以直接更改

  1. # -*- coding: utf-8 -*-
  2. # Define here the models for your scraped items
  3. #
  4. # See documentation in:
  5. # https://doc.scrapy.org/en/latest/topics/items.html
  6. import scrapy
  7. class AmazonItem(scrapy.Item):
  8. name = scrapy.Field() # 商品名
  9. price= scrapy.Field() # 价格
  10. delivery=scrapy.Field() # 配送方式

修改 DianShang—>spiders—>amazon.py,使用item

  1. # -*- coding: utf-8 -*-
  2. import scrapy
  3. from scrapy import Request # 导入模块
  4. from DianShang.items import AmazonItem # 导入item
  5. class AmazonSpider(scrapy.Spider):
  6. name = 'amazon'
  7. allowed_domains = ['amazon.cn']
  8. # start_urls = ['http://amazon.cn/']
  9. # 自定义配置,注意:变量名必须是custom_settings
  10. custom_settings = {
  11. 'REQUEST_HEADERS': {
  12. 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
  13. }
  14. }
  15. def start_requests(self):
  16. r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",
  17. headers=self.settings.get('REQUEST_HEADERS'),)
  18. yield r1
  19. def parse(self, response):
  20. # 商品详细链接
  21. detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
  22. # print(detail_urls)
  23. for url in detail_urls:
  24. yield Request(url=url,
  25. headers=self.settings.get('REQUEST_HEADERS'), # 请求头
  26. callback=self.parse_detail, # 回调函数
  27. dont_filter=True # 不去重
  28. )
  29. def parse_detail(self, response): # 获取商品详细信息
  30. # 商品名,获取第一个结果
  31. name = response.xpath('//*[@id="productTitle"]/text()').extract_first()
  32. if name:
  33. name = name.strip()
  34. # 商品价格
  35. price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first()
  36. # 配送方式,*[1]表示取第一个标签,也就是b标签
  37. delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first()
  38. print(name,price,delivery)
  39. # 生成标准化数据
  40. item = AmazonItem() # 执行函数,默认是一个空字典
  41. # 增加键值对
  42. item["name"] = name
  43. item["price"] = price
  44. item["delivery"] = delivery
  45. return item # 必须要返回

Item PipeLine

上面的item只是返回了一个字典,并没有真正存储到MongoDB中。需要PipeLine将数据储存到MongoDB中

修改 DianShang—>pipelines.py。注意:from_crawler,open_spider,close_spider,process_item这4个方法,必须要定义才行!

  1. # -*- coding: utf-8 -*-
  2. # Define your item pipelines here
  3. #
  4. # Don't forget to add your pipeline to the ITEM_PIPELINES setting
  5. # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
  6. from pymongo import MongoClient
  7. class MongodbPipeline(object):
  8. def __init__(self, host, port, db, table):
  9. self.host = host
  10. self.port = port
  11. self.db = db
  12. self.table = table
  13. @classmethod
  14. def from_crawler(cls, crawler):
  15. """
  16. Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完
  17. 成实例化
  18. """
  19. HOST = crawler.settings.get('HOST')
  20. PORT = crawler.settings.get('PORT')
  21. DB = crawler.settings.get('DB')
  22. TABLE = crawler.settings.get('TABLE')
  23. return cls(HOST, PORT, DB, TABLE)
  24. def open_spider(self, spider):
  25. """
  26. 爬虫刚启动时执行一次
  27. """
  28. # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port))
  29. self.client = MongoClient(host=self.host, port=self.port)
  30. def close_spider(self, spider):
  31. """
  32. 爬虫关闭时执行一次
  33. """
  34. self.client.close()
  35. def process_item(self, item, spider):
  36. # 操作并进行持久化
  37. d = dict(item)
  38. if all(d.values()):
  39. self.client[self.db][self.table].insert(d)
  40. print("添加成功一条")

open_spider,close_spider这2个方法,只会执行一次。

process_item呢?就不一定了。有多少次请求,就执行多少次!每次会插入一条记录

修改 DianShang\settings.py,增加MongoDB连接信息。

最后一行增加以下信息:
  1. # MongoDB连接信息
  2. HOST="127.0.0.1"
  3. PORT=27017
  4. DB="amazon" # 数据库名
  5. TABLE="goods" # 表名

再修改 DianShang\settings.py,开启MongoDB的PipeLine

这里的300,表示优先级

  1. ITEM_PIPELINES = {
  2. 'DianShang.pipelines.MongodbPipeline': 300,
  3. }

注意:由于在pipelines.py中,定义的是MongodbPipeline。所以配置文件这里,必须要一一匹配,否则报错:

  1. raise NameError("Module '%s' doesn't define any object named '%s'" % (module, name))

先启动MongoDB,打开cmd窗口,输入命令

  1. C:\Users\xiao>mongod

如果输出:

  1. [thread1] waiting for connections on port 27017

则表示启动成功了!

Pipeline是不会帮你自动创建数据库的,所以需要手动创建。

进入数据库,创建数据库amazon

  1. C:\Users\xiao>mongo
  2. > use amazon
  3. switched to db amazon
  4. >

重新运行 bin.py,输出:

  1. ...
  2. Apple 苹果 手机 iPhone X 深空灰色 64G 7,288.00 新思维官方旗舰店
  3. 添加成功一条
  4. ...

使用 MongoDB客户端打开表goods,效果如下:

Day137 爬虫系列之第4章-scrapy框架 - 图20