scrapy详解基础,一篇到进阶门槛

前言

将学scrapy过程中一些常用到但是不需要经常更改的代码记录下来,以便后面使用,同时打卡中间一些常错点。

创建和启用

常见的创建scrapy语句:

scrapy startproject 项目名

scrapy genspider 爬虫名 域名

scrapy crawl 爬虫名

例如:

scrapy详解基础,一篇到进阶门槛_第1张图片

这些文件分别是:

  • scrapy.cfg: 项目的配置文件
  • picture/: 该项目的python模块。之后您将在此加入代码。
  • picture/items.py: 项目中的item文件.
  • picture/pipelines.py: 项目中的pipelines文件.
  • picture/settings.py: 项目的设置文件.
  • picture/spiders/: 放置spider代码的目录.

下一步是创建爬虫,但是在创建爬虫之前,我们查看scrapy中文文档,发现scrapy其实一共提供了五种模板spider

它们分别是

Spider

Spider是最简单的spider。每个其他的spider必须继承自该类(包括Scrapy自带的其他spider以及您自己编写的spider)。 Spider并没有提供什么特殊的功能。 其仅仅请求给定的 start_urls/start_requests ,并根据返回的结果(resulting responses)调用spider的 parse 方法。

CrawlSpider

爬取一般网站常用的spider。其定义了一些规则(rule)来提供跟进link的方便的机制。 也许该spider并不是完全适合您的特定网站或项目,但其对很多情况都使用。 因此您可以以其为起点,根据需求修改部分方法。当然您也可以实现自己的spider。

XMLFeedSpider

XMLFeedSpider被设计用于通过迭代各个节点来分析XML源(XML feed)。 迭代器可以从 iternodes , xml , html 选择。 鉴于 xml 以及 html 迭代器需要先读取所有DOM再分析而引起的性能问题, 一般还是推荐使用 iternodes 。 不过使用 html 作为迭代器能有效应对错误的XML。

CSVFeedSpider

该spider除了其按行遍历而不是节点之外其他和XMLFeedSpider十分类似。 而其在每次迭代时调用的是 parse_row() 

SitemapSpider

SitemapSpider使您爬取网站时可以通过 Sitemaps 来发现爬取的URL。

其支持嵌套的sitemap,并能从 robots.txt 中获取sitemap的url

在本文中我主要学习了前两种模板,后面三个,暂时没有学习,有兴趣的朋友可以前往scrapy-chs.readthedocs.io/zh_CN/0.24/topics/spiders.html自行查看。

那么怎么创建不同类型的模板呢?

scrapy详解基础,一篇到进阶门槛_第2张图片

scrapy详解基础,一篇到进阶门槛_第3张图片

首先创建最基本的spider

再创建一个crawlspider

scrapy详解基础,一篇到进阶门槛_第4张图片

解释 一下,本次爬取网站为www.meishij.net/china-food/caixi/,一个美食网站,反爬措施比较少,方便新手学习。同时,对想学做菜的朋友们也有点用

scrapy详解基础,一篇到进阶门槛_第5张图片

Scrapy框架逻辑

scrapy详解基础,一篇到进阶门槛_第6张图片

在这里,我就不解释这个框架图的意思了,网上有很多,摆出来主要方便后续解释运行逻辑。

想一想,如果我们不用scrapy框架来实现爬虫,应该是一个什么流程

scrapy详解基础,一篇到进阶门槛_第7张图片

因此,我们一步步通过这个流程,来看在scrapy的哪些位置进行相应的修改,处理

XXXXSpider.py

首先访问网站,第一步和第二步都在一个文件中,就是自己创建的spider中,笔者先用最基本的spider进行试验,等整个流程结束后,再用crawlspider做进阶试验

打开之前创建的picturespider.py文件

import scrapy


class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    start_urls = ['http://www.meishij.net/china-food/caixi//']

    def parse(self, response):
        pass

这里有三行代码

      name = 'picturespider'

     name是创建的spider的名字,必须唯一,是到时候启动spider进行爬取的参数

    allowed_domains = ['www.meishij.net/china-food/caixi/']

可选。包含了spider允许爬取的域名(domain)列表(list)。 当 OffsiteMiddleware 启用时, 域名不在列表中的URL不会被跟进。

    start_urls = ['http://www.meishij.net/china-food/caixi//']

URL列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。 因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。

这行非必需品,但是如果要去掉,就必须要用替代品,怎么替代以及为什么要替代,请往下看

Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。

对spider来说,爬取的循环类似下文:

  1. 以初始的URL初始化Request,并设置回调函数。 当该request下载完毕并返回时,将生成response,并作为参数传给该回调函数。

    spider中初始的request是通过调用 start_requests() 来获取的。 start_requests() 读取 start_urls 中的URL, 并以 parse 为回调函数生成 Request 

  2. 在回调函数内分析返回的(网页)内容,返回 Item 对象或者 Request 或者一个包括二者的可迭代容器。 返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的callback函数(函数可相同)。

  3. 在回调函数内,您可以使用 选择器(Selectors) (您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 来分析网页内容,并根据分析的数据生成item。

  4. 最后,由spider返回的item将被存到数据库(由某些 Item Pipeline 处理)或使用 Feed exports 存入到文件中。

接下来,我将逐行对上述内容进行解释

start_urls

首先是第一条,这里的初始URL就是下文中的start_urls,后面的 ”spider中初始的request是通过调用 start_requests() 来获取的” 什么意思呢,这里也没有start_requests()

start_urls列表。当没有指定特定的url时,spider将从该列表中开始抓取,相当于爬虫访问的第一个网页,或者是第一批网页,因为这是一个列表,可以添加多个初始url

访问的网址有了,那么准备开始访问,而且访问的流程一般是用一个request去访问,然后返回得到一个response对象

现在的问题是谁去访问,怎么返回。这应该是个函数,是需要我们自己定义访问的函数嘛?对,但不完全对,也可以不自定义

在scrapy框架中已经给我们定义的默认的函数也就是上面的start_requests()

构造request:

在scrapy中,如果我们使用 start_urls = ['http://www.meishij.net/china-food/caixi//']这个 列表作为起始的访问网址,且没有其他要求,框架会在暗地里使用一个叫做make_requests_from_url()方法(该方法的默认实现是使用start_urls的url生成request,该方法接受一个URL并返回用于爬取的 Request 对象。 该方法在初始化request时被 start_requests() 调用,也被用于转化url为request。

默认未被复写(overridden)的情况下,该方法返回的Request对象中, parse() 作为回调函数,dont_filter参数也被设置为开启)。该方法仅仅会被scrapy调用一次

request构造好了,谁用这个对象去访问呢,那就是start_requests()方法,这个方法也是框架内有的,不过在暗地里。

该方法的默认实现是使用 start_urls 的url生成Request,如果您想要修改最初爬取某个网站的Request对象,可以重写(override)该方法

我们如果不使用start_urls列表,我们可以直接越过上面两步,直接重写start_requests()方法,进行访问,并返回response对象,重写start_requests()一般也有两种写法,一种是使用上面的make_requests_from_url()方法,另一种就是不用,在此,我将不重写以及两种重写的方法都尝试一遍,

#重写start_request()方法,使用
import scrapy


class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    start_urls = ['http://www.meishij.net/china-food/caixi//']

    def parse(self, response):
        print(response.status)#输出访问起始网址的状态码

状态码为200,访问成功

#重写方法,并使用make_requests_from_url()
import scrapy


class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    #start_urls = ['http://www.meishij.net/china-food/caixi//']


    def parse(self, response):
        print(response.status)
#重写方法,并使用make_requests_from_url()
import scrapy


class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    #start_urls = ['http://www.meishij.net/china-food/caixi//']
    start_urls=[]

    def start_requests(self): 
    	url='http://www.meishij.net/china-food/caixi//'
    	yield self.make_requests_from_url(url)#该方法默认的回调函数即为下面的parse

    def parse(self, response):
        print(response.status)

#完全重写方法
import scrapy


class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    #start_urls = ['http://www.meishij.net/china-food/caixi//']
    start_urls=[]

    def start_requests(self): 
    	url='http://www.meishij.net/china-food/caixi//'
    	yield scrapy.Request(url,callback=self.parse)#与上一个方法相比,需要我们使用Request来生成request对象,并且指明回调函数的名字,因此,我们可以在这里可以不使用parse名字,而是自己定义

    def parse(self, response):
        print(response.status)

好了,三种方法,我都试过了,但是为什么我们要需要重写这个方法呢,就用默认的列表可以实现目的,另外两种(也可以说是一种方法)方法一般在什么情况下使用呢?

重写start_requests()情景:

一、伪装浏览器,防止反爬。

因为有很多网站是拒绝爬虫访问,所以要使用Request对象把爬虫伪装成浏览器访问,怎么伪装呢?比如说这样:

#设置用户代理为浏览器类型
    headers = {"User-Agent" : "Mozilla***************************************"}
 
    def start_requests(self):
        url = "http://www.meishij.net/china-food/caixi//"
        yield Request(url, headers=self.headers, callback=self.parse)

当然 ,还有很多其他可以加的,大家可以网上看怎么构造request对象的伪装信息,这是就不扩展了

二、urls列表内容太多,分页爬取。有时scrapy默认的start_requests无法满足我们的需求,例如分页爬取,那就要对它进行重写,添加更多操作。

比如我们想要爬取美食网中川菜的前10页就行了,毕竟,也学不完那么 多菜,我们怎么实现对这10个网址的访问呢(每页一个网址),有三种方法

1、是使用start_urls列表,在列表中存在10条网址

2、重写start_requests(),在方法中构造10个网址,逐条访问,如下:

import scrapy


class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    #start_urls = ['http://www.meishij.net/china-food/caixi//']
    start_urls=[]

    def start_requests(self): 
    	base='https://www.meishij.net/china-food/caixi/chuancai/'
    	for i in range(10):
    		request_url=base+str(i)
    		yield scrapy.Request(request_url,callback=self.parse)

    def parse(self, response):
        print(response.url)#打印访问的网址,看是否是前10页

scrapy详解基础,一篇到进阶门槛_第8张图片

这里,没有按顺序显示,为什么呢,因为scrapy是基于pythonr的twist异步框架写的,可以并行爬取,提高速度

(图片中后面加的参数 --nolog 的意思就是不要输出日志信息,方便查看输出内容,但是如果程序没有按照预定的目标输出,还是打开日志显示比较好,方便进行调试)

3、第三种方法就是在parse方法解析网页的过程中,跳转到下一页,这个等进入parse学习的时候,再举例

 

三、改写post请求。scrapy默认发起的是get请求,如果你想发起post请求,利用start_request方法,对该方法进行改写,进行post请求。

def start_requests(self):
    return [scrapy.FormRequest("http://www.example.com/login",
                               formdata={'user': 'john', 'pass': 'secret'},
                               callback=self.logged_in)]

def logged_in(self, response):
    # here you would extract links to follow and return Requests for
    # each of them, with another callback
    pass

四、实现动态入口。通过之前的学习我们知道scrapy是将start_urls作为爬取入口,而且每次都是直接硬编码进去一个或多个固定的URL,现在假设有这么个需求:爬虫需要先从数据库里面读取目标URL再依次进行爬取,这时候固定的start_urls就显得不够灵活了,好在scrapy允许我们重写start_requests方法来满足这个需求。

from scrapy.spider import BaseSpider#这个例子来自网上,仅作举例
from scrapy.http import Request
import MySQLdb
import os


class PdfSpider(BaseSpider):
    name = "pdfspider"
    target_dir = "/home/newspace/pdfs"
    def start_requests(self):
    db = MySQLdb.connect(host="localhost", user="root", passwd="123456", db="scrapy",                                                             
    charset='utf8',
    use_unicode=False)
    cur = db.cursor()
    cur.execute("select url from tab_url")
    for url in cur.fetchall():
        yield Request(url[0], self.parse)
    cur.close()
    db.close()

当然 ,还有一些 其他的用途,比较常见的,我就遇见这些,大家可以指点我一下,还有哪些常用的。

parse回调函数

当response没有指定回调函数时(在上面的start_request()方法中),该方法是Scrapy处理下载的response的默认方法。

parse 负责处理response并返回处理的数据以及(/或)跟进的URL。 Spider 对其他的Request的回调函数也有相同的要求。

该方法及其他的Request回调函数必须返回一个包含 Request 及(或) Item 的可迭代的对象。

现在,我们编写parse方法对网页进行解析,得到数据

首先,确定目标,看我们需要 哪些信息

scrapy详解基础,一篇到进阶门槛_第9张图片

从图片中,我们可以看出,对于每道菜,可以简单地提取以下几个信息:

name:菜名

steps:做菜需要 几步

costTIme;需要的时间

ways:烹饪的方法

tasty;口味

imgUrl:保存这道菜的图片url,方便我们后续下载

当然 我们可以再加一个内容:texturl(详情页的url地址)

接下来进入网页分析,然后编写解析代码

scrapy详解基础,一篇到进阶门槛_第10张图片

可以看出 每道菜对应一个div块,因此 我们首先要做的是就是怎么从网页中提取这些div块出来,然后再在每个div块中提取每道 菜的对应信息

selector选择器

当抓取网页时,你做的最常见的任务是从HTML源码中提取数据。现有的一些库可以达到这个目的:

  • BeautifulSoup 是在程序员间非常流行的网页分析库,它基于HTML代码的结构来构造一个Python对象, 对不良标记的处理也非常合理,但它有一个缺点:慢。
  • lxml 是一个基于 ElementTree (不是Python标准库的一部分)的python化的XML解析库(也可以解析HTML)。

Scrapy提取数据有自己的一套机制。它们被称作选择器(seletors),因为他们通过特定的 XPath 或者 CSS 表达式来“选择” HTML文件中的某个部分。

XPath 是一门用来在XML文件中选择节点的语言,也可以用在HTML上。 CSS 是一门将HTML文档样式化的语言。选择器由它定义,并与特定的HTML元素的样式相关连。

Scrapy选择器构建于 lxml 库之上,这意味着它们在速度和解析准确性上非常相似。

在scrapy的parse中,参数response对象以 .selector 属性提供了一个selector选择器,用于提取网页数据

我将使用 Scrapy shell (提供交互测试)和位于Scrapy文档服务器的一个样例页面,来解释如何使用选择器:

http://doc.scrapy.org/en/latest/_static/selectors-sample1.html

这里是它的HTML源码:


 
  
  Example website
 
 
  
 

效果如下:

scrapy详解基础,一篇到进阶门槛_第11张图片

首先, 我们打开shell:

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

接着,当shell载入后,您将获得名为 response 的shell变量,其为响应的response, 并且在其 response.selector 属性上绑定了一个selector。

scrapy详解基础,一篇到进阶门槛_第12张图片

因为我们处理的是HTML,选择器将自动使用HTML语法分析。

那么,通过查看 HTML code 该页面的源码,我们构建一个XPath来选择title标签内的文字:

>>> response.selector.xpath('//title/text()')
[]

由于在response中使用XPath、CSS查询十分普遍,因此,Scrapy提供了两个实用的快捷方式: response.xpath() 及 response.css():

>>> response.xpath('//title/text()')
[]
>>> response.css('title::text')
[]

如你所见, .xpath() 及 .css() 方法返回一个类 SelectorList 的实例, 它是一个新选择器的列表。这个API可以用来快速的提取嵌套数据

为了提取真实的原文数据,你需要调用 .extract() 方法如下:

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

注意CSS选择器可以使用CSS3伪元素(pseudo-elements)来选择文字或者属性节点:

>>> response.css('title::text').extract()
[u'Example website']

下面我总结 一下通过XPath和css解析获取数据的常见方式

对内容的提取一般有两个方面,一是页面标签中的内容如上面的text就是标签内的内容,二是标签中的属性值 ,比如 图片的href属性

XPath:

IN:response.xpath('//title/text()').extract()#titlte标签全网页唯一
OUT:[u'Example website']

IN: response.xpath('//title/text()')
OUT:[]

#提取标签属性值
IN:response.xpath('//base/@href').extract()
OUT:[u'http://example.com/']


IN:response.xpath('//a[contains(@href, "image")]/@href').extract()#获取href属性为image的a标签 的href属性
OUT:[u'image1.html',
      u'image2.html',
      u'image3.html',
      u'image4.html',
      u'image5.html']

IN:dlist=response.selector.xpath("//div[@class='house_left left'])#提取class属性为house_left left的所有div 块,XPath中我常用这种解析

使用相对XPaths

记住如果你使用嵌套的选择器,并使用起始为 / 的XPath,那么该XPath将对文档使用绝对路径,而且对于你调用的 Selector 不是相对路径。

比如,假设你想提取在 

 元素中的所有 

 元素。首先,你将先得到所有的 

 元素:

>>> divs = response.xpath('//div')

开始时,你可能会尝试使用下面的错误的方法,因为它其实是从整篇文档中,而不仅仅是从那些 

 元素内部提取所有的 

 元素:

>>> for p in divs.xpath('//p'):  # this is wrong - gets all 

from the whole document ... print p.extract()

下面是比较合适的处理方法(注意 .//p XPath的点前缀):

>>> for p in divs.xpath('.//p'):  # extracts all 

inside ... print p.extract()

另一种常见的情况将是提取所有直系 

 的结果:

>>> for p in divs.xpath('p'):
...     print p.extract()

CSS:

# css方法:
#  提取标签内容
IN: response.css('title::text').extract()
OUT:[u'Example website']
IN:response.css('title::text')
OUT:[]

# 提取标签属性值
IN:response.css('base::attr(href)').extract()
OUT:[u'http://example.com/']

IN:dlist=response.css('li.houst_ctn')
css提取嵌套的选择器我觉得是比XPath用得更顺手一点,比如拿我们川菜的来直接实战

CSS嵌套提取

先看网页

scrapy详解基础,一篇到进阶门槛_第13张图片

我们发现所有的菜都是一个class属性为listtylel的div块,那我们能直接取这种div块嘛,先试一下(先切换shell,访问该川菜页面)

scrapy详解基础,一篇到进阶门槛_第14张图片

可以,正好对应第一页的18道 菜,但是有的时候,可能 要经过一次嵌套来获取这些列表块。

现在dlists 中的每一个元素都是一个selectorc对象

什么意思呢?就是我们对里面的内容还要再进行提取,不能直接拿到我们要数据

scrapy详解基础,一篇到进阶门槛_第15张图片

接下来开始提取

菜名name

scrapy详解基础,一篇到进阶门槛_第16张图片

分析:使用extract()提取的是一个列表,我们要取里面的值,有两种方法,一种是列表取值,另一种是还有一种方法是extract_first()方法取出列表的第一个值

scrapy详解基础,一篇到进阶门槛_第17张图片

步骤steps

观察steps的位置,属于标签内容,但是内容中还有其他的,直接用取标签内容的方法,取出的值还有我们下一个要取的值,所以接下来的这四个值,我们可以用相同的办法获取:正则

selector对象可以使用正则进行内容匹配,函数 为.re_first()提取满足的第一个

scrapy详解基础,一篇到进阶门槛_第18张图片

因此 对于后面三个值,用同样的方法,就可以了,这里不一个一个进行,直接在下面代码里完整贴出

图片imgUrl

import scrapy


class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    #start_urls = ['http://www.meishij.net/china-food/caixi//']
    start_urls=[]

    def start_requests(self): 
    	base='https://www.meishij.net/china-food/caixi/chuancai/'
    	for i in range(1):
    		if i >0:#不能用0,否则首页不对
    			request_url=base+str(i)
    		else:
    			request_url=base
    		yield scrapy.Request(request_url,callback=self.parse)

    def parse(self, response):
        #print(response.url)#打印访问的网址,看是否是前10页
        #提取每道菜对应的div块列表
        
        dlists=response.css('div.listtyle1')
        
        for food in dlists:
        	name=food.css('a::attr(title)').extract_first()
        	steps=food.re_first('li1">([0-9]+)步')
        	costTIme=food.re_first('大概([0-9]+)分钟')
        	ways=food.re_first('li2">(.*?) /')
        	tasty=food.re_first('li2">.*?/ (.*?)

在这里需要 注意一点,就是在start_request()中构造url时,首页后面是不加0,否则不是我们需要 的当前页面,这一点需要 大家在实践中通过对当前网址检查,没有绝对

在最后我仅对name进行了输出

scrapy详解基础,一篇到进阶门槛_第19张图片

好了,现在已经 完成 对数据的解析

按照流程应该做是就是定义数据格式了,这个在items.py中

items.py

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

import scrapy

class PictureItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    name=scrapy.Field()
    steps=scrapy.Field()
    costTime=scrapy.Field()
    ways=scrapy.Field()
    tasty=scrapy.Field()
    imgUrl==scrapy.Field()

在这里,我们需要 对前面的spider文件进行一下修改,通过这个 PictureItem来存储数据,具体如下:

import scrapy
from picture.items import PictureItem#如果这里导入出现 找不到该模块的错了,可能 是因为使用的集成开发环境对根目录的设定的原因

class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    #start_urls = ['http://www.meishij.net/china-food/caixi//']
    start_urls=[]

    def start_requests(self): 
    	base='https://www.meishij.net/china-food/caixi/chuancai/'
    	for i in range(1):
    		if i >0:#不能用0,否则首页不对
    			request_url=base+str(i)
    		else:
    			request_url=base
    		yield scrapy.Request(request_url,callback=self.parse)

    def parse(self, response):
        #print(response.url)#打印访问的网址,看是否是前10页
        #提取每道菜对应的div块列表
        
        dlists=response.css('div.listtyle1')
        
        for food in dlists:
        	item=PictureItem()

        	item['name']=food.css('a::attr(title)').extract_first()
        	item['steps']=food.re_first('li1">([0-9]+)步')
        	item['costTime']=food.re_first('大概([0-9]+)分钟')
        	item['ways']=food.re_first('li2">(.*?) /')
        	item['tasty']=food.re_first('li2">.*?/ (.*?)

如果出现 No module named item的错误,可能主要有以下几个原因:

1.爬虫名字和项目名字一样,导致导入模块时出错:改爬虫或者项目名称

2.模块不存在:检查你的项目中的items,看看有没有出错

3.模块没有保存:在编辑好items模块时,记得运行编译

4.模块名字和引入的不一样:自行检查

5.手动添加自己创建的scrapy文件夹的路径:例如

import sys

sys.path.append(文件路径)

现在该是数据的存储,我将其放入mysql数据据中,在pipelines.py文件中

Pipelines.py

当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。

每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。

以下是item pipeline的一些典型应用:

  • 清理HTML数据
  • 验证爬取的数据(检查item包含某些字段)
  • 查重(并丢弃)
  • 将爬取结果保存到数据库中

编写你自己的item pipeline很简单,每个item pipiline组件是一个独立的Python类,同时必须实现以下方法:

process_item(itemspider)

每个item pipeline组件都需要调用该方法,这个方法必须返回一个 Item (或任何继承类)对象, 或是抛出 DropItem 异常,被丢弃的item将不会被之后的pipeline组件所处理。

参数:
  • item (Item 对象) – 被爬取的item
  • spider (Spider 对象) – 爬取该item的spider

此外,他们也可以实现以下方法:

open_spider(spider)

当spider被开启时,这个方法被调用。

 

close_spider(spider)

当spider被关闭时,这个方法被调用

在本例中,我将实现两个pipeline类,一个用于将存储到mysql数据库,另一个用于保存菜品的图片,将使用到scrapy框架自带的一个图片下载类

MySqlPipeline类

(现在应该已经 在mysql数据库中创建 了一个名为eat_menu的数据库,和一个库中一个名为menus的表)

scrapy详解基础,一篇到进阶门槛_第20张图片

先使用硬编码 的方式连接数据库

from scrapy import Request
import pymysql

class MySqlPipeline(object):
    """docstring for MySqlPipeline"""
    #mysql 连接方式
    def open_spider(self,spider):
        #连接数据库
        self.connect=pymysql.connect(
            host='127.0.0.1',
            port=3306,
            db='eat_menu',
            user='root',
            passwd='Woshi123',
            charset='utf8',
            use_unicode=True)
        self.cursor=self.connect.cursor()

或者通过从setting.cfg配置文件中读取信息的方式来动态连接

def __init__(self,host,user,password,database,port):
        self.host = host
        self.user = user
        self.password = password
        self.database = database
        self.port = port
    @classmethod
    def from_crawler(cls,crawler):
        return cls(
            host = crawler.settings.get("MYSQL_HOST"),
            user = crawler.settings.get("MYSQL_USER"),
            password = crawler.settings.get("MYSQL_PASSWD"),
            database = crawler.settings.get("MYSQL_DBNAME"),
            port = crawler.settings.get("MYSQL_PORT"),
        )

    def open_spider(self, spider):
        '''负责连接数据库'''
        self.db = pymysql.connect(host=self.host,user=self.user,passwd=self.password,db=self.database,charset="utf8",port=self.port)#这里必须要用指名的参数传入方式,否则可能会报错
        self.cursor = self.db.cursor()

    def  process_item(self,item,spider):

        self.cursor.execute(
            "insert into menus(name,steps,costTime,ways,tasty) values ('%s','%s','%s','%s','%s')"%(item['name'][0],item['steps'],item['costTime'],item['ways'],item['tasty']))
        self.db.commit()
        return item
    def close_spider(self,spider):
        self.db.close()
        

现在到最后一步了,就是配置settings.cfg

settings.cfg

对于这个 配置文件的修改,最简单的来说,我们应该有下几个方面:

1.君子协议。原来的这个值为True,我们需要修改为False,否则很多的网站我们都无法爬取

2.重写header信息。

3.爬虫中间件和下载中间件。这两个地方可以用来开启自己写的中间件(不仅得有装备,还要给装备开关打开,才能使用)

scrapy详解基础,一篇到进阶门槛_第21张图片

4.开启pipeline的使用。后面的数字代表了执行的先后顺序,如果定义有不同的pipeline,用于规定先后顺序,越小越先执行,比如,我们如果再定义一个用于数据清洗的pipline,则序号就应该比它大

scrapy详解基础,一篇到进阶门槛_第22张图片

5.设置数据库连接的参数。IMAGES_STORE是设置菜品图片存储的位置,稍后写imagePipeline类时会用到

IMAGES_STORE = 'D:\\Spide\\picture'
# IMAGES_THUMBS = {
#     'small': (50, 50),
#     'big': (270, 270),
# }

MYSQKL_HOST= 'localhost' 
MYSQL_DBNAME= 'eat_menu' 
MYSQL_USER= 'root' 
MYSQL_PASSWD='Woshi123'
MYSQL_PORT = 3306

 

这样,基本上就算是大功告成,现在试运行一下

再打开数据库瞧瞧

scrapy详解基础,一篇到进阶门槛_第23张图片

现在我们再回过头看前面 还有哪些坑

1.在parse方法中实现翻页,构造url继续访问

import scrapy
from picture.items import PictureItem

class PicturespiderSpider(scrapy.Spider):
    name = 'picturespider'
    allowed_domains = ['www.meishij.net/china-food/caixi/']
    start_urls = ['http://www.meishij.net/china-food/caixi//']
    page=2
    # start_urls=[]

    # def start_requests(self): 
    # 	base='https://www.meishij.net/china-food/caixi/chuancai/'
    # 	for i in range(1):
    # 		if i >0:#不能用0,否则首页不对
    # 			request_url=base+str(i)
    # 		else:
    # 			request_url=base
    # 		yield scrapy.Request(request_url,callback=self.parse)

    def parse(self, response):
        #print(response.url)#打印访问的网址,看是否是前10页
        #提取每道菜对应的div块列表
        
        dlists=response.css('div.listtyle1')
       
        
        for food in dlists:
        	item=PictureItem()

        	item['name']=food.css('a::attr(title)').extract_first()
        	item['steps']=food.re_first('li1">([0-9]+)步')
        	item['costTime']=food.re_first('大概([0-9]+)分钟')
        	item['ways']=food.re_first('li2">(.*?) /')
        	item['tasty']=food.re_first('li2">.*?/ (.*?)

输出翻页的网址,看是否正确

scrapy详解基础,一篇到进阶门槛_第24张图片

2.实现用于保存图片的imgPipeline

3.spider中间件和下载中间件

4.crawlspider类的使用

5.代理池的使用

6.分布式爬虫

7.selenium的使用(解决Ajex加载和登录检验)

上面的剩下6个,等知识储备够了,再来详写。

 

 

 

你可能感兴趣的:(实战中练爬虫,python,mysql)