推荐我的个人博客 http://blog.wuzhenyu.com.cn

scrapy 是一个用 python 语言编写的,为了爬取网站数据,提取结构性数据而编写的应用框架。

环境

本文使用的环境:
python 3.5.2
pip 9.0.1
操作系统: Ubuntu 16.04

pythton 环境搭建

在官网下载 Ubuntu 环境下的python3.5的安装包,安装,安装完成后,检查一下 python 的安装情况,一般pyhton安装的时候,pip 也是一起安装好的,如果没有安装完全,再将 pip 也一起安装好。

虚拟环境搭建

现在Ubuntu默认是安装 python2.7 的,避免两个环境之间切换的麻烦,我们安装 python 虚拟环境来解决这个问题。

pip install virtualenv
pip install virtualwrapper
pip list # 查看已安装

virtualenv 能够通过根据不同的 python 版本创建对应不同版本的虚拟环境,virtualwrapper 能够方便的在不同的虚拟环境之间进行切换。安装完成之后,下面我们创建一个 python3.5.2 版本的虚拟环境

source /usr/local/bin/virtualwrapper.sh #这个与 windows 不一样,需要先执行一下脚本才能生效,大家可以打开这个文件看一下
# 创建一个名为 py3Scrapy 的虚拟环境
mkvirtualenv py3Scrapy -p /usr/bin/python3.5
# workon 查看创建好的虚拟环境,虚拟环境的保存路径可以通过 `VIRTUALENV_PYTHON` 来配置
workon
workon py3Scrapy # 进入选择的虚拟环境

如下图所示:

python的版本也能查看得到,进入虚拟环境之后,在shell前面会出现虚拟环境的名称,退出虚拟环境

deactivate

好了,创建好环境之后,现在来开始我们的 scrapy 之旅吧。

scrapy 环境搭建

scrapy 是基于 twisted 框架的,大家会发现,安装 scrapy 的时候,会需要安装很多包。

pip install scrapy

使用 pip 进行安装,方便,但是这种默认的安装方式,实在官网下载安装包来进行安装的,比较慢,大家可以使用豆瓣源来进行安装

pip install scrapy -i https://pypi.douban.com/simple

这种方式,下载会非常的快,安装其他的包都可以使用这种方法,但是,如果使用豆瓣源安装的时候,提示找不到符合版本的安装包,那就使用第一种办法进行下载安装,因为豆瓣源可能没有官网那么及早更新。

因为每个人的环境都可能存在差异,安装过程中会出现一些问题。当如果报错,twisted 安装失败的时候,建议从官网下载 twisted 安装包,自行进行安装,安装完成之后,再接着继续上面 scrapy 的安装,安装完成之后,检查一些安装结果

scrapy -h

使用 scrapy 获取某一个文章的信息

好了,环境准备好之后,接下来我们来分析一下伯乐在线的文章网页结构

分析伯乐在线某一篇文章的网页结构和url

伯乐在线网站不需要我们先登录,然后才能访问其中的内容,所以不需要先模拟登录,直接就能访问网页。伯乐在线地址为 https://www.jobbole.com,这上面的文章质量还是不错的,大家有时间可以看看。

我们随便找一篇文章试图来分析一下,比如 http://blog.jobbole.com/111469/,F12进入浏览器调试窗口,从全文分析,比如我们想获取文章标题,文章内容,文章创建时间,点赞数,评论数,收藏数,文章所属类别标签,文章出处等信息

使用 scrapy shell 的方法获取解析网页数据

打开文章链接,我们获取到的是一个html页面,那么如何获取上面所说的那些数据呢,本文通过 CSS 选择器来获取(不了解 CSS selector的小伙伴可以先去熟悉一下 http://www.w3school.com.cn/cssref/css_selectors.asp)。 scrape 为我们提供了一个 shell 的环境,可以方便我们进行调试和实验,验证我们的css 表达式能够成功获取所需要的值。下面启动 scrapy shell

scrapy shell "http://blog.jobbole.com/111469/"

scrapy 将会帮助我们将http://blog.jobbole.com/111469/这个链接的数据捕获,现在来获取一下文章标题,在浏览器中找到文章标题,inspect element 审查元素,如下图所示:

文章标题为王垠:如何掌握所有的程序语言,从上图获知,这个位于一个 class 名为 entry-header 的 div 标签下的子标签 h1 中,那我们在 scrapy shell 通过 css 选择器来获取一下,如下图所示:

仔细查看上图,注意一些细节。通过 response.css 方法,返回的结果是一个 selector,不是字符串,在这个 selector 的基础上可以继续使用 css 选择器。通过 extract() 函数获取提取的标题内容,返回结果是一个 list,注意,这里是一个 list ,仍然不是字符串 str,使用 extract()[0] 返回列表中的第一个元素,即我们需要的标题。

但是,如果标题没有获取到,或者选择器返回的结果为空的话,使用 extract()[0] 就会出错,因为试图对一个空链表进行访问,这里使用 extract_first() 方法更加合适,可是使用一个默认值,当返回结果为空的时候,返回这个默认值

extract_first("")   # 默认值为 ""

此处仅仅是将 title 标题作为一个例子进行说明,其他的就不详细进行解释了,主要代码如下所示:

    title = response.css(".entry-header h1::text").extract()[0]match_date = re.match("([0-9/]*).*",response.css(".entry-meta-hide-on-mobile::text").extract()[0].strip())if match_date:create_date = match_date.group(1)votes_css = response.css(".vote-post-up h10::text").extract_first()if votes_css:vote_nums = int(votes_css)else:vote_nums = 0ma_fav_css = re.match(".*?(\d+).*",response.css(".bookmark-btn::text").extract_first())if ma_fav_css:fav_nums = int(ma_fav_css.group(1))else:fav_nums = 0ma_comments_css = re.match(".*?(\d+).*",response.css("a[href='#article-comment'] span::text").extract_first())if ma_comments_css:comment_nums = int(ma_comments_css.group(1))else:comment_nums = 0tag_lists_css = response.css(".entry-meta-hide-on-mobile a::text").extract()tag_lists_css = [ele for ele in tag_lists_css if not ele.strip().endswith('评论')]tags = ','.join(tag_lists_css)content = response.css(".entry *::text").extract()

解释一下 create_date,通过获取到的值,存在其他非时间的数据,通过 re.match 使用正则表达式来提取时间。

好了,所有需要的值都提取成功后,下面通过 scrapy 框架来创建我们的爬虫项目。

创建爬虫项目

开始我们的爬虫项目

scrapy startproject ArticleSpider

scrapy 会为我们创建一个名为 ArticleSpider 的项目
进入到 ArticleSpider 目录,使用basic模板创建

scrapy genspider jobbole blog.jobbole.com

创建完成之后,我们使用 pycharm 这个IDE打开我们创建的爬虫项目,目录结构如下所示:

├── ArticleSpider
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── __pycache__
│   ├── settings.py
│   ├── spiders
│   │   ├── __init__.py
│   │   ├── jobbole.py
│   │   └── __pycache__
└── scrapy.cfg

我们可以在 items.py 里面定义数据保存的格式,在 middlewares.py 定义中间件,在 piplines.py 里面处理数据,保存到文件或者数据库中等。在 jobbole.py 中对爬取的页面进行解析。

下面,我们首先需要做的,就是利用我们编写的 css 表达式,获取我们提取的文章的值。在 jobbole.py 中,我们看到

class JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/all-posts/']def parse(self, response):pass

scrapy 为我们创建了一个 JobboleSpider 的类,name 是爬虫项目的名称,同时定义了域名以及爬取的入口链接。scrapy 初始化的时候,会初始化 start_urls 入口链接列表,然后通过 start_requests 返回 Request 对象进行下载,调用 parse 回调函数对页面进行解析,提取需要的值,返回 item。

所以,我们需要做的,就是将我们在上一小节编写的代码放在 parse 函数中,同时,将 start_urls 的值,改为上面我们在 scrapy shell 爬取的页面的地址http://blog.jobbole.com/111469/,因为我们这里还没有讲到通过 item 获取我们提取的值,此处你可以通过 print() 函数将值进行打印。在 shell 中启动爬虫(先进入我们的工程目录)

scrapy crawl jobbole

既然我们使用了 pycharm 这个IDE,那么我们就不用 shell 来启动爬虫,在 ArticleSpider 目录下创建一个 main.py 文件

from scrapy.cmdline import execute
import sys
import ossys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute(["scrapy", "crawl", "jobbole"])

上面的代码,就是将当前项目路径加入到 path 中,然后通过调用scrapy 命令行来启动我们的工程。然后,通过设置断点调试,一步一步查看我们的提取的变量的值是否正确。

注意:启动之前,将 settings.py 中的 ROBOTSTXT_OBEY 这个参数设置为 False

这样,我们就爬取到了伯乐在线的这一篇文章了。

扩展,爬取所有的文章

既然我们已经能够获取到某一篇文章的数据,那么下面就来获取所有文章的链接。

扩展一:获取所有 url 链接

伯乐在线所有文章链接的入口地址为 http://blog.jobbole.com/all-posts/,通过浏览器进入调试模式查看文章列表的链接,如下图所示

文章链接是在 id 为 archive 的 div 标签下的子 div 标签之下, class 为 post-thumb,这个下面的子标签 a 的 href 属性,仍使用上面说的 scrapy shell 的方法,如下图所示

可以看出,获得了当前页面所有的文章的 url,这仅仅是当前页面的所有 url,我们还需要获取下一页的 url,然后通过下一页的 url 进入到下一页,获取下一页的所有文章的 url,依次类推,知道爬取完所有的文章 url。

在文章列表的最后,有翻页,分析如下

下一页是 class 为 next page-numbers 的 a 标签中,如下图

既然现在所有的 url 都能够获取到了,那么现在我们将 jobbole.py 中的 parse 函数修改一下

def parse(self, response):post_nodes = response.css("#archive .floated-thumb .post-thumb")    # a selector, 可以在这个基础上继续做 selectorfor post_node in post_nodes:post_url = post_node.css("a::attr(href)").extract_first("")yield Request(url=parse.urljoin(response.url, post_url),callback=self.parse_detail)# 必须考虑到有前一页,当前页和下一页链接的影响,使用如下所示的方法next_url = response.css("span.page-numbers.current+a::attr(href)").extract_first("")if next_url:yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)def parse_detail(self, response):
"""作为回调函数,在上面调用"""
title = response.css(".entry-header h1::text").extract()[0]match_date = re.match("([0-9/]*).*",response.css(".entry-meta-hide-on-mobile::text").extract()[0].strip())if match_date:create_date = match_date.group(1)votes_css = response.css(".vote-post-up h10::text").extract_first()if votes_css:vote_nums = int(votes_css)else:vote_nums = 0ma_fav_css = re.match(".*?(\d+).*",response.css(".bookmark-btn::text").extract_first())if ma_fav_css:fav_nums = int(ma_fav_css.group(1))else:fav_nums = 0ma_comments_css = re.match(".*?(\d+).*",response.css("a[href='#article-comment'] span::text").extract_first())if ma_comments_css:comment_nums = int(ma_comments_css.group(1))else:comment_nums = 0tag_lists_css = response.css(".entry-meta-hide-on-mobile a::text").extract()tag_lists_css = [ele for ele in tag_lists_css if not ele.strip().endswith('评论')]tags = ','.join(tag_lists_css)# cpyrights = response.css(".copyright-area").extract()content = response.css(".entry *::text").extract()

1. 获取文章列表页中的文章url,交给 scrapy 下载后并进行解析,即调用 parse 函数解析
2. 然后获取下一页的文章 url,按照1 2 循环

对于 parse 函数,一般做三种事情
a. 解析返回的数据 response data
b. 提取数据,生成 ITEM
c. 生成需要进一步处理 URL 的 Request 对象

某些网站中,url 仅仅只是一个后缀,需要将当前页面的url+后缀进行拼接,使用的是 parse.urljoin(base, url),如果 urljoin 中的 url 没有域名,将使用base进行拼接,如果有域名,将不会进行拼接,此函数在 python3 的 urllib 库中。Request(meta参数):meta参数是一个字典{},作为回调函数的参数

这样,我们就获得了所有的文章

扩展二:使用item,并保存图片到本地

上一小节提到了, parse 函数提取数据之后,生成 item,scrapy 会通过 http 将 item 传到 pipeline 进行处理,那么这一小节,我们使用 item 来接收 parse 提取的数据。在 items.py 文件中,定义一个我们自己的数据类 JobBoleArticleItem,并继承 scrapy.item 类

class JobBoleArticleItem(scrapy.Item):title = scrapy.Field()          # Field()能够接收和传递任何类型的值,类似于字典的形式create_date = scrapy.Field()    # 创建时间url = scrapy.Field()            # 文章路径front_img_url_download = scrapy.Field()fav_nums = scrapy.Field()       # 收藏数comment_nums = scrapy.Field()   # 评论数vote_nums = scrapy.Field()      # 点赞数tags = scrapy.Field()           # 标签分类 labelcontent = scrapy.Field()        # 文章内容object_id = scrapy.Field()      # 文章内容的md5的哈希值,能够将长度不定的 url 转换成定长的序列

Field() 对象,能够接收和传递任何类型的值,看源代码,就能发现,Field() 类继承自 dict 对象,具有字典的所有属性。

注意,在上面定义的类中,我们增加了一个新的成员变量 front_img_url_download,这是保存的是文章列表中,每一个文章的图片链接。我们需要将这个图片下载到本地环境中。既然使用了 item 接收我们提取的数据,那么 parse 函数就需要做相应的改动

def parse(self, response):post_nodes = response.css("#archive .floated-thumb .post-thumb")    # a selector, 可以在这个基础上继续做 selectorfor post_node in post_nodes:post_url = post_node.css("a::attr(href)").extract_first("")img_url = post_node.css("a img::attr(src)").extract_first("")yield Request(url=parse.urljoin(response.url, post_url),meta={"front-image-url":img_url}, callback=self.parse_detail)# 必须考虑到有前一页,当前页和下一页链接的影响,使用如下所示的方法next_url = response.css("span.page-numbers.current+a::attr(href)").extract_first("")if next_url:yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)

同时,解析函数 parse_detail 也需要修改,将数据保存到我们的item中,只需要添加下面的部分就可

    front_img_url = response.meta.get("front-image-url", "")article_item = JobBoleArticleItem() # 实例化 item 对象# 赋值 item 对象article_item["title"] = titlearticle_item["create_date"] = create_datearticle_item["url"] = response.urlarticle_item["front_img_url_download"] = [front_img_url] # 这里传递的需要是列表的形式,否则后面保存图片的时候,会出现类型错误,必须是可迭代对象article_item["fav_nums"] = fav_numsarticle_item["comment_nums"] = comment_numsarticle_item["vote_nums"] = vote_numsarticle_item["tags"] = tags# article_item["cpyrights"] = cpyrightsarticle_item["content"] = ''.join(content)      # 取出的 content 是一个 list ,存入数据库的时候,需要转换成字符串article_item["object_id"] = gen_md5(response.url)yield article_item

这里,parse 函数成功生成了我们定义的 item 对象,将数据传递到 pipeline。那么,图片链接已经获取到了,我们如下将图片下载下来呢。

解释一下上面代码中的 front-img-url,这个是在 parse 函数中作为参数 meta 传递给 Request() 函数,回调函数调用 parse_detail,返回的 response 对象中的 meta 成员,将包含这个元素, meta 就是一个字典, response.meta.get(“front-image-url”) 将获取到我们传递过来的图片url

scrapy 提供了一个 ImagesPipeline 类,可直接用于图片操作,只需要我们在 settings.py 文件中进行配置即可。

在 settings.py 中,有一个配置参数为 ITEM_PIPELINE,这其实就是一个字典,当需要用到 pipeline 时,就需要在这个字典中进行配置,字典中存在的, scrapy 才会使用。字典中的 key 就是 pipeline 的类名,后面的数字表示优先级,数字越小表示越先调用,越大越靠后。既然我们现在需要使用到 scrapy 提供的图片下载功能,那么需要在这个字典中配置 ImagesPipeline

ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1,
}

同时,还需要在 settings.py 中配置,item 中哪一个字段是图片 url,以及图片需要存放什么位置

IMAGES_URLS_FIELD = "front_img_url_download"     # ITEM 中的图片 URL,用于下载
PROJECT_IMAGE_PATH = os.path.abspath(os.path.dirname(__file__))   # 获取当前文件所在目录
IMAGES_STORE = os.path.join(PROJECT_IMAGE_PATH, "images")         # 下载图片的保存位置

这些参数,可以在 ImagesPipeline 类的源代码中查看到

注意:上面配置好后,上面的代码是在工程路径下面创建一个 images 的目录,用于保存图片,运行 main.py,可能会出现如下错误: no module named PIL,这是因为图片操作需要 pillow 库,只需要安装即可
pip install pillow,快速安装,就按照我上面说的豆瓣源的方法。
还可能出现”ValueError: Missing scheme in request url: h”的错误,这是因为图片操作,要求 front_img_url_download 的值为 list 或者可以迭代的对象,所以我们在 parse 函数中给 item 赋值的时候, front_img_url_download 就是赋值的 list 对象

好了,这些注意了之后,应该能够下载图片了。

扩展三:使用 itemloader

相信大家已经发现,虽然使用了 item,但是使用 css selecotor,我们的 parse 函数显得很长,而且,当数据量越来越大之后,一大堆的 css 表达式是很难维护的。在加上正则表达式的提取,代码会显得很臃肿。这里,给大家推荐是用 itemloader。itemloader 可以看成是一个容器。

首先,在 items.py 中,我们需要定义一个继承自 ItemLoader 的类

class ArticleItemLoader(ItemLoader):"""自定义 ItemLoader, 就相当于一个容器"""# 这里表示,输出获取的 ArticleItemLoader 提取到的值,都是 list 中的第一个值# 如果有的默认不是取第一个值,就在 Field() 中进行修改default_output_processor = TakeFirst()

将默认输出函数定为 TakeFirst(),即取结果 list 中的第一个值,定义了 ItemLoader 类之后,需要修改 jobbole.py 中的 parse_detail 函数了,现在就不再直接使用 css selector 了,使用 itemloader 中的 css 进行数据提取,新的 parse_detail 如下所示:

def parse_detail(self, response):front_img_url = response.meta.get("front-image-url", "")item_loader = ArticleItemLoader(item=JobBoleArticleItem(), response=response)article_item_loader = JobBoleArticleItem()item_loader.add_css("title", ".entry-header h1::text")  # 通过 css 选择器获取值item_loader.add_value("url", response.url)item_loader.add_css("create_date", ".entry-meta-hide-on-mobile::text")item_loader.add_value("front_img_url_download", [front_img_url])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("vote_nums", ".vote-post-up h10::text")item_loader.add_css("tags", ".entry-meta-hide-on-mobile a::text")item_loader.add_css("content", ".entry *::text")item_loader.add_value("object_id", gen_md5(response.url))# item_loader.add_xpath()# item_loader.add_value()article_item_loader = item_loader.load_item()yield article_item_loader

这样,数据提取就全部交给了 itemloader 来执行了。代码整体都简洁和工整了很多。ItemLoader 有三个方法用于提取数据,分别是 add_css(), add_xpath(), add_value(),前两个分别是 css 选择器和 xpath 选择器,如果是值,就直接使用 add_value() 即可。

最后 load_item() 函数,将根据上面提供的规则进行数据解析,每一个解析的值都是以 list 结果的形式呈现,同时,将结果赋值 item。

但是,大家应该已经发现,之前我们直接使用 css selector 提取数据的时候,对于某些数据,需要使用正则表达式进行匹配才能获取所需的值,这里什么都没做,仅仅是通过 itemloader 提取了数据而已。所以,我们还需要重新定义我们的 item 类,这些操作在 item 中进行处理。修改 items.py 中的 JobBoleArticleItem 类,具体如下:

class JobBoleArticleItem(scrapy.item):title = scrapy.Field()create_date = scrapy.Field(     # 创建时间input_processor = MapCompose(get_date),output_processor = Join(""))url = scrapy.Field()            # 文章路径front_img_url_download = scrapy.Field(    # 文章封面图片路径,用于下载,赋值时必须为数组形式# 默认 output_processor 是 TakeFirst(),这样返回的是一个字符串,不是 list,此处必须是 list# 修改 output_processoroutput_processor = MapCompose(return_value))front_img_url = scrapy.Field()fav_nums = scrapy.Field(        # 收藏数input_processor=MapCompose(get_nums))comment_nums = scrapy.Field(    # 评论数input_processor=MapCompose(get_nums))vote_nums = scrapy.Field(       # 点赞数input_processor=MapCompose(get_nums))tags = scrapy.Field(           # 标签分类 label# 本身就是一个list, 输出时,将 list 以 commas 逗号连接input_processor = MapCompose(remove_comment_tag),output_processor = Join(","))content = scrapy.Field(        # 文章内容# content 我们不是取最后一个,是全部都要,所以不用 TakeFirst()output_processor=Join(""))object_id = scrapy.Field()      # 文章内容的md5的哈希值,能够将长度不定的 url 转换成定长的序列

input_processor 对传入的值进行预处理, output_processor 对处理后的值按照规则进行处理和提取,比如 TakeFirst() 就是对处理的结果取第一个值。

input_processor = MapCompose(func1, func2, func3, ...) 这行代码,说明的是, Item 传入的这个字段的值,将会分别调用 MapCompose 中的所有传入的方法进行逐个处理,这个方法也是可以是 lambda 的匿名函数。

因为上面定义 ArticleItemLoader 类的时候,使用了默认的 default_output_processor,如果不想使用默认的这个方法,就在 Field() 中,使用 output_processor 参数覆盖默认的方法,哪怕什么都不做,也不会使用默认方法获取数据了。对上面那些方法定义如下:

def get_nums(value):"""通过正则表达式获取 评论数,点赞数和收藏数"""re_match = re.match(".*?(\d+).*", value)if re_match:nums = (int)(re_match.group(1))else:nums = 0return numsdef get_date(value):re_match = re.match("([0-9/]*).*?", value.strip())if re_match:create_date = re_match.group(1)else:create_date = ""return create_datedef remove_comment_tag(value):"""去掉 tag 中的 “评论” 标签"""if "评论" in value:return ""else:return valuedef return_value(value):"""do nothing, 只是为了覆盖 ItemLoader 中的 default_processor"""return value

千万注意:这些方法,每一个最后,都必须有 return,否则程序到后面将获取不到这个字段的数据,再次访问这个字段的时候,就会报错。

扩展四:将数据导出到 json 文件中

好了,既然已经将数据通过 ItemLoader 获取到了,那么我们现在就将数据从 pipeline 输出到 json 文件中。

将数据以 json 格式输出,可以通过 json 库的方法,也可以使用 scrapy.exporters 的方法。

json 库

我们已经知道,对数据的处理,scrapy 是在 pipeline 中进行的,所以,我们需要在 pipelines.py 中定义我们对数据的导出操作。创建一个新类

class JsonWithEncodingPipeline(object):"""处理 item 数据,保存为json格式的文件中"""def __init__(self):self.file = codecs.open('article.json', 'w', encoding='utf-8')def process_item(self, item, spider):lines = json.dumps(dict(item), ensure_ascii=False) + '\n'   # False,才能够在处理非acsii编码的时候,不会出错,尤其#中文self.file.write(lines)return item     # 必须 returndef spider_close(self, spider):"""把文件关闭"""self.file.close()

__init__() 构造对象的时候,就打开文件,scrapy 会调用 process_item() 函数对数据进行处理,在这个函数中,将数据以 json 的格式写入文件中。操作完成之后,将文件关闭。思路很简单。

scrapy.exporters 的方式

class JsonExporterPipeline(object):def __init__(self):"""先打开文件,传递一个文件"""self.file = open('articleexporter.json', 'wb')#调用 scrapy 提供的 JsonItemExporter导出json文件self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)self.exporter.start_exporting()def spider_close(self, spider):self.exporter.finish_exporting()self.file.close()def process_item(self, item, spider):self.exporter.export_item(item)return item

scrapy.exporters 提供了几种不同格式的文件支持,能够将数据输出到这些不同格式的文件中,查看 JsonItemExporter 源码即可获知

__all__ = ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter','CsvItemExporter', 'XmlItemExporter', 'JsonLinesItemExporter','JsonItemExporter', 'MarshalItemExporter']

这些就是 scrapy 支持的文件。方法名称都差不多,这算是 scrapy 运行 pipeline 的模式,只需要将逻辑处理放在 process_item(),scrapy 就会根据规则对数据进行处理。

当然,要想使我们写的数据操作有效,别忘记了,在 settings.py 中进行配置

ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1,'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,'ArticleSpider.pipelines.JsonExporterPipeline':3,
}

扩展五:将数据存储到 MySQL 数据库

前面介绍了将数据以 json 格式导出到文件,那么将数据保存到 MySQL 中,如何操作,相信大家已经差不多了然于胸了。这里也介绍两种方法,一种是通过 MySQLdb 的API来实现的数据库存取操作,这种方法简单,适合用与数据量不大的场合,如果数据量大,数据库操作的速度跟不上数据解析的速度,就会造成数据拥堵。那么使用第二种方法就更好,使用 twisted 框架提供的异步操作方法,不会造成拥堵,速度更快。

既然是入 MySQL 数据库,首先肯定是需要创建数据库表了。表结构如下图所示:

上图中有一个字段的值,我没有讲述怎么取,就是 front_img_path 这个值,大家在数据库入库的时候,直接用空置或者空字符串填充即可。这个字段是保存图片在本地保存的路径,这个需要在 ImagesPipe 的 item_completed(self, results, item, info) 方法中的 results 参数中获取。

好了,数据库表创建成功之后,下面就来将数据入库了。

MySQLdb 的方法入库

class MysqlPipeline(object):def __init__(self):# 连接数据库self.conn = MySQLdb.connect('192.168.0.101', 'spider', 'wuzhenyu', 'article_spider', charset="utf8", use_unicode=True)self.cursor = self.conn.cursor()def process_item(self, item, spider):insert_sql = """insert into article(title, create_date, url, url_object_id, front_img_url, front_img_path, comment_nums, fav_nums, vote_nums, tags, content) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s')""" % (item["title"], item["create_date"], item["url"], item["object_id"],item["front_img_url"],item["front_img_path"], item["comment_nums"], item["fav_nums"], item["vote_nums"], item["tags"],item["content"])self.cursor.execute(insert_sql)self.conn.commit()def spider_close(self, spider):self.cursor.close()self.conn.close()

如果对 API 想了解的更多,就去阅读 python MySQLdb 的相关API文档说明,当然,要想这个生效,首先得在 settings.py 文件中将这个 pipeline 类加入 ITEM_PIPELINE 字典中

ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1,'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,'ArticleSpider.pipelines.JsonExporterPipeline':3,'ArticleSpider.pipelines.MysqlPipeline': 4,
}

通过 Twisted 框架提供的异步方法入库

class MysqlTwistedPipeline(object):"""利用 Twisted API 实现异步入库 MySQL 的功能Twisted 提供的是一个异步的容器,MySQL 的操作还是使用的MySQLDB 的库"""def __init__(self, dbpool):self.dbpool = dbpool@classmethoddef from_settings(cls, settings):"""被 spider 调用,将 settings.py 传递进来,读取我们配置的参数模仿 images.py 源代码中的 from_settings 函数的写法"""# 字典中的参数,要与 MySQLdb 中的connect 的参数相同dbparams = dict(host = settings["MYSQL_HOST"],db = settings["MYSQL_DBNAME"],user = settings["MYSQL_USER"],passwd = settings["MYSQL_PASSWORD"],charset = "utf8",cursorclass = MySQLdb.cursors.DictCursor,use_unicode = True)# twisted 中的 adbapi 能够将sql操作转变成异步操作dbpool = adbapi.ConnectionPool("MySQLdb", **dbparams)return cls(dbpool)def process_item(self, item, spider):"""使用 twisted 将 mysql 操作编程异步执行"""query = self.dbpool.runInteraction(self.do_insert, item)query.addErrback(self.handle_error) # handle exceptionsdef handle_error(self, failure):"""处理异步操作的异常"""print(failure)def do_insert(self, cursor, item):"""执行具体的操作,能够自动 commit"""print(item["create_date"])insert_sql = """insert into article(title, create_date, url, url_object_id, front_img_url, front_img_path, comment_nums, fav_nums, vote_nums, tags, content) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s');""" % (item["title"], item["create_date"], item["url"], item["object_id"], item["front_img_url"],item["front_img_path"], item["comment_nums"], item["fav_nums"], item["vote_nums"], item["tags"],item["content"])# self.cursor.execute(insert_sql, (item["title"], item["create_date"], item["url"], item["object_id"],#                                 item["front_img_url"], item["front_img_path"], item["comment_nums"],#                                 item["fav_nums"], item["vote_nums"], item["tags"], item["content"]))print(insert_sql)cursor.execute(insert_sql)

博主也是近一个多星期开始学习爬虫的 scrapy 框架,对 Twisted 框架也不怎么熟悉,上面的代码是一个例子,大家可以看下注释,等以后了解更多会补充更多相关知识。

需要提到的是,上面定义的 from_settings(cls. settings) 这个类方法, scrapy 会从 settings.py
文件中读取配置进行加载,这里将 MySQL 的一些配置信息放在了 settings.py 文件中,然后使用 from_settings 方法直接获取,在 settings.py 中需要添加如下代码:

# MySQL params
MYSQL_HOST = ""
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "spider"
MYSQL_PASSWORD = ""

本篇文章,主要以 scrapy 框架爬取伯乐在线文章为例,简要介绍了 scrapy 爬取数据的一些方法,博主也是最近才开始学习爬虫,有不对的地方还请大家能够指正。


windows 中安装环境与 Ubuntu 会有一些不一样,而且如果使用的是 python3.x 版本,会要求 vc++ 的版本比较高,最好安装的是 visual studio 2015 以上的版本。否则会很麻烦。

对于 python2.7版本,在windows中,可以安装 VSForPython27.msi ,依赖的那些库应该就不会再出错了。

scrapy简单入门 - 爬取伯乐在线所有文章相关推荐

  1. scrapy初始第一波——爬取伯乐在线所有文章

    1 前言    要说到爬虫界的明星,当属我们的python,而这得益于我们的爬虫明星框架--scrapy,这就让我们不得不学习它,这几天刚好用它做一些小demo,就将其总结一下,希望能对大家的学习爬虫 ...

  2. Scrapy爬取伯乐在线所有文章

    Scrapy爬取伯乐在线所有文章 1.目标分析 2.Spiders的编写 2.1.网站结构分析 2.2.获取当页文章URL 2.3.获取文章的信息 2.4.文章列表下一页 2.4.编写spiders. ...

  3. Python scrapy爬虫爬取伯乐在线全部文章,并写入数据库

    伯乐在线爬虫项目目的及项目准备: 1.使用scrapy创建项目 2.创建爬虫,bole 域名 jobbole.com 3.Start_urls = ['http://blog.jobbole.com/ ...

  4. scrapy 解析css,Scrapy基础(六)————Scrapy爬取伯乐在线一通过css和xpath解析文章字段...

    上次我们介绍了scrapy的安装和加入debug的main文件,这次重要介绍创建的爬虫的基本爬取有用信息 通过命令(这篇博文)创建了jobbole这个爬虫,并且生成了jobbole.py这个文件,又写 ...

  5. 边学边敲边记之爬虫系列(八):Scrapy系统爬取伯乐在线

    一.前言 上一篇边学边敲边记爬虫系列七给大家仔细讲解了如何用Xpath分类爬取医疗信息网站医疗器材名称和介绍图片,以及三种最常用的存储方法. 本篇是本系列的第八篇了,今天给大家讲讲如何用Scrapy分 ...

  6. 利用Scrapy爬取伯乐在线文章并存取到mysql数据库

    1.观察网址直接从(http://blog.jobbole.com/all-posts/)入手爬取伯乐在线所有文章,常规cmd创建项目 2.spider中采取xpath和css选择器提取语法,提取出想 ...

  7. Scrapy爬取伯乐在线的所有文章

    本篇文章将从搭建虚拟环境开始,爬取伯乐在线上的所有文章的数据. 搭建虚拟环境之前需要配置环境变量,该环境变量的变量值为虚拟环境的存放目录 1. 配置环境变量 2.创建虚拟环境 用mkvirtualen ...

  8. scrapy | 爬取伯乐在线全部博文(xpath/css/itemload三种提取方法,同步、异步方式存入MySQL)

    1.目标 伯乐在线网站地址:http://blog.jobbole.com/all-posts/ 爬取伯乐在线的所有文章信息,包括图片网址,标题,发表日期,标签,点赞数,评论数等 将爬取的数据保存至数 ...

  9. Python爬虫爬取伯乐在线

    一.环境搭建 1.创建环境 执行pip install scrapy安装scrapy 使用scrapy startproject ArticleSpider创建scrapy项目 使用pycharm导入 ...

最新文章

  1. Java实现斐波那契数列Fibonacci
  2. linux 补丁脚本,Linux上打patch补丁包脚本全解
  3. 第一章 SDN介绍 (附件4)【 SDN的核心技术:【OpenFlow】】
  4. mysql 导入文件夹_MySQL-导入与导出
  5. 趣学java,编程趣学习app
  6. 搞定mac的bashrc
  7. 小米8吃鸡战斗服务器响应超时,小米8使用1天真实体验,看完再决定买不买?
  8. 2T比特每秒!瞻博推出业界最快防火墙
  9. Django之Django debug toolbar调试工具
  10. 读react.js小书 01
  11. 微信小程序人脸识别认证-微信开放接口
  12. CAD插件学习系列教程(八) tiff及jpg影像按真实坐标插入CAD,打包分享共4款
  13. [CM311-1A]-买了一个机顶盒准备刷成 Linux 盒子!
  14. python跳一跳编程构造_Python玩“跳一跳” iOS+Win 硬件实现
  15. linux用 弹出光驱 cdromeject_sw,Linux_Linux系统下光驱软开关与限速,一、光驱的软开关:eject -r cdro - phpStudy...
  16. html css使用特殊自定义字体避免侵权
  17. 实测:一周不更新文章头条号指数会掉多少?
  18. C#下支付宝新版异步回调数据处理及校验(需支付宝提供的AopSdk)
  19. Redis学习笔记1-理论篇
  20. ssh服务器(自己的一点心得和操作总结)

热门文章

  1. sqlserver:文件和打印共享资源(*.*.*.210)处于联机状态,但未对连接尝试做出响应。
  2. ccpc 2016 合肥站 (5道题)
  3. Python爬虫-DAY5淘宝页面爬取
  4. 论文投稿指南——收藏|SCI写作投稿发表全流程
  5. 中国联通携号转网已上线,需输入手机号申请,即可换成39元套餐
  6. php 实线,PHP实现的功能是显示8条基色色带
  7. Centos修改IP
  8. 51单片机0-99秒表计数器+60秒倒计时(数码管两位数)
  9. Java从网上读取docx文件到内存
  10. chrome浏览器字体小于12px的解决方式