Python爬虫学习笔记兼教程(二)——实战伯乐在线

引言

说完了爬虫的一些基础,这一篇是记录如何爬取伯乐在线的所有文章的内容。


爬取策略

打开伯乐在线,可以看到这里,网站已经提供了所有文章的一个分类,也就不需要深度优先或者广度优先算法了。

然后分页提取的时候,选择把下一页的链接提取出来,这样当页面总数增加的时候也不需要去更改源码或者配置文件。而不是选择更改链接里面的页面数字。


创建Scrapy项目

安装Scrapy

安装Scrapy可以上这里下载。

如果中途提示需要什么依赖,也可以在里面下载。

创建

创建的时候,在你的Pycharm里面新建一个项目先,然后在这个项目的目录下面用Shift+鼠标右键,打开Powershell或者命令行,执行scrapy startproject 爬虫名字,再用Pycharm打开就可以了。

遇到的问题

首先遇到了这样一个错误

1
UserWarning: You do not have a working installation of the service_identity module: 'cannot import name 'opentype''.

提示我缺少一个包service_identity,然后还是上刚刚的网站下载service_identity这个库。

安装service_identity这个库的时候报错:

1
pyasn1-modules 0.2.1 has requirement pyasn1<0.5.0,>=0.4.1, but you'll have pyasn1 0.1.9 which is incompatible.

提示我pyasn1-modules这个库版本太低。还是去刚刚的网站下载pyasn1这个库,不是pyasn1-modules

安装pyasn1的时候提示

1
Cannot uninstall 'pyasn1'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.

然后使用了这个解决方案。不知道正确与否,但是好使。


Scrapy目录

  • settings: 设置
  • pipelines: 和数据存储有关的文件
  • middlewares: 中间件是介入到Scrapy的spider处理机制的钩子框架
  • items: 定义数据保存的格式
  • spiders文件夹:具体的一个网站的爬虫文件

创建一个网站爬虫文件

在爬虫的根目录下面执行scrapy genspider jobbole blog.jobbole.com,这是以伯乐在线为例的。可以看到spiders文件夹里面就有了一个jobbole.py文件。


让Scrapy可以调试

在爬虫根目录下执行scrapy crawl jobbole,如果没有报错,显示的是Spider closed (finished)。就在根目录下面创建一个main.py文件,结构如下:

写入下面内容:

1
2
3
4
5
6
from scrapy.cmdline import execute
import sys
import os

sys.path.append(os.path.dirname(os.path.abspath(__file__))) # 得到目录
execute(["scrapy", "crawl", "jobbole"])

这样就可以在Pycharm里面调试Scrapy框架了。

PS:将settings.py文件里面的ROBOTSTXT_OBEY = False这一项注释去掉并改为False。这一项的意思是是否根据网站的Robots协议来爬取。


提取一个文章的内容

xpath语法

在网页中找到某一个东西的位置,就免不了要用xpath或者css,下面是一些常用语法:

xpath提取

打开一篇文章,用F12看网页源码,用选择按钮,选择文章标题,然后右键生成xpath。

经过对比会发现,chrome和火狐浏览器生成的xpath不一样,而且火狐浏览器的xpath不能正确读取出标题。

1
2
chrome: //*[@id="post-114159"]/div[1]/h1
火狐浏览器:/html/body/div[1]/div[3]/巴拉巴拉

因为浏览器在F12里面源码是经过浏览器渲染而来,定位不准。

PS:div的id必须是全局唯一的,通常可以用这个来定位div。或者calss也可以。

单页面调试

每次验证xpath是否正确的时候,都要重新启动一次框架,而启动一次框架很费时,所以Scrapy提供一种shell调试,可以对一个页面进行反复调试。

在爬虫项目根目录下执行scrapy shell 网址,就可以进入一个交互,然后对单个页面进行反复调试而只用访问一次。

用xpath提取内容

  • 标题
  • 时间
  • 正文
  • 点赞数
  • 评论数
  • 收藏数

标题

首先在页面上按F12打开分析框。然后用元素选择工具选择标题,如下图:

可以看到,这个标题是在h1标签里面的,然后h1标签上面有两个div,第一个div最后面有一个id,所以我们就用这个id来定位这个div,进而定位标题的位置。

在之前说的单页调试shell先读取一个页面,以这一页为例,就是:

1
scrapy shell http://blog.jobbole.com/114159/

然后执行:

1
2
3
4
response.xpath('//*[@id="post-114159"]/div[1]/h1')

输出:
[<Selector xpath='//*[@id="post-114159"]/div[1]/h1' data='<h1>通过可写文件获取 Linux root 权限的 5 种方法</h1>'>]

可以看到输出为一个选择器,在Scrapy里面,用xpath选择的返回值是一个选择器,为了可以二次筛选。而且data里面带有标签,这里在xpath最后加上text()可以去掉前后的标签,再用extract方法读取这个data,这个方法会忽略这个节点下面的所有子节点。

1
2
3
4
response.xpath('//*[@id="post-114159"]/div[1]/h1/text()').extract()

输出:
['通过可写文件获取 Linux root 权限的 5 种方法']

返回值是一个数组,取第一个,就得到了标题。

1
title = response.xpath('//*[@id="post-114159"]/div[1]/h1/text()').extract()[0]

并把这个保存到title变量里面,写到jobbole.pyparse方法里面。在最后会给出jobbole.py的完整代码。

时间

同理可找到时间的位置:

在一个p标签下面,可以看到这个p标签有一个class属性,查找可以知道,这个class的值是惟一的,于是就可以用这个来定位。

还是现在shell里面调试看看是不是正确的。

1
2
3
4
response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract()[0]

输出:
'\r\n\r\n 2018/06/26 · '

strip方法去除这些回车和空格,用replace方法去除后面的·,再调用strip方法去除空格。写入爬虫项目里面去。

点赞数

还是一样找到点赞数的位置:

是在一个span标签下面的,分析发现这个标签的class中,vote-post-up是全局唯一的,就用这个来定位。

还是现在shell里面调试看看是不是正确的。

1
2
3
4
response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0]

输出:
'1'

这里面要用到一个contains方法,是一个xpath内置的,看这个span标签的class有很多个(每一个之间用空格分开了),直接用@class='vote-post-up'是找不到的,因为class并不只有这个,所以要用这个函数,表示某个属性里面包含这个值。

收藏数、评论数、正文

同理,就是收藏数评论数需要用正则表达式来匹配一下。

jobble.py文件全代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# -*- coding: utf-8 -*-
import scrapy
import re


class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/114159/']

def parse(self, response):
# //*[@id="post-114159"]/div[1]/h1
title = response.xpath('//*[@id="post-114159"]/div[1]/h1/text()').extract()[0]
creat_date = response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract()[0].strip().strip().replace("·", "").strip()
praise_nums = response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0]
fav_nums = response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0]
match_re = re.match(r".*?(\d+).*", fav_nums)
if match_re:
fav_nums = match_re.group(1)
else:
fav_nums = 0
comment_nums = response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
match_re = re.match(r".*?(\d+).*", comment_nums)
if match_re:
comment_nums = match_re.group(1)
else:
comment_nums = 0
content = response.xpath("//div[@class='entry']").extract()[0]
tags_list = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract()
tags_list = [element for element in tags_list if not element.strip().endswith("评论")]
tags = ','.join(tags_list)
pass

用css提取内容

常见的css语法

jobble.py文件全代码

因为和xpath类似,就不再一一介绍,直接给出全部代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding: utf-8 -*-
import scrapy
import re


class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/114159/']

def parse(self, response):
title = response.css(".entry-header h1::text").extract()[0]
creat_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().strip().replace("·", "").strip()
praise_nums = response.css(".vote-post-up h10::text").extract()[0]
fav_nums = response.css(".bookmark-btn::text").extract()[0]
match_re = re.match(r".*?(\d+).*", fav_nums)
if match_re:
fav_nums = match_re.group(1)
else:
fav_nums = 0
comment_nums = response.css("a[href='#article-comment'] span::text").extract()[0]
match_re = re.match(r".*?(\d+).*", comment_nums)
if match_re:
comment_nums = match_re.group(1)
else:
comment_nums = 0
content = response.css("div.entry").extract()[0]
tags_list = response.css("p.entry-meta-hide-on-mobile a::text").extract()
tags_list = [element for element in tags_list if not element.strip().endswith("评论")]
tags = ','.join(tags_list)
pass

extract方法可以更改为extract_first,可以防止后面取数组第一个的时候异常报错。


所有文章URL提取

上面是针对某一面的字段解析,但是我们需要的是从第一面开始,提取出每一面的所有文章URL,然后进入具体文章URL并解析字段,然后再进入下一页,进行上述工作。

每一页URL提取

这一页开始,可以找到每一个文章的URL是位于这样的位置:

1
post_urls = response.css('#archive .floated-thumb .post-thumb a::attr(href)').extract()

交给Scrapy下载则用其自带的一个类Resquest:

1
2
3
4
from scrapy.http import Request
from urllib import parse
for post_url in post_urls:
yield Request(url=parse.urljoin(response.url, post_url), callback=self.parse_detail)

其中用上了parse类,是因为有些网站的href提取出来的并不是完整的链接,所以要用urljoin方法拼出完整的链接。

下一页链接提取

1
2
3
next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
if next_url:
yield Request(url=parse.urljoin(response.url, post_url), callback=self.parse)

jobble.py文件全代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request
import re
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
:param response:
:return:
"""
# 解析文章列表页中的文章URL
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) # 用yield交给Scrapy下载

# 提取下一页,并交给Scrapy下载
next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
if next_url:
yield Request(url=parse.urljoin(response.url, post_url), callback=self.parse) # 用yield交给Scrapy下载

def parse_detail(self, response):
# 提取文章的具体字段

# css
title = response.css(".entry-header h1::text").extract()[0]
creat_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().strip().replace("·", "").strip()
praise_nums = response.css(".vote-post-up h10::text").extract()[0]
fav_nums = response.css(".bookmark-btn::text").extract()[0]
match_re = re.match(r".*?(\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(r".*?(\d+).*", comment_nums)
if match_re:
comment_nums = int(match_re.group(1))
else:
comment_nums = 0
content = response.css("div.entry").extract()[0]
tags_list = response.css("p.entry-meta-hide-on-mobile a::text").extract()
tags_list = [element for element in tags_list if not element.strip().endswith("评论")]
tags = ','.join(tags_list)
pass

集中存储数据

提供的items模块,就是用来集中保存数据的。在items里面写一个新的class,然后写入想保存的量。

1
2
3
4
5
6
7
8
9
10
11
12
class JobboleArticleItem(scrapy.Item):
title = scrapy.Field()
creat_date = scrapy.Field()
url = scrapy.Field()
url_object_id = scrapy.Field()
front_image_url = scrapy.Field()
front_image_path = scrapy.Field()
praise_nums = scrapy.Field()
comment_nums = scrapy.Field()
fav_nums = scrapy.Field()
tags = scrapy.Field()
content = scrapy.Field()

然后在jobbole.pyparse_detail方法最后加入:

1
2
3
4
5
6
7
8
9
10
11
12
article_item = JobboleArticleItem() # 记得在最开始import进来这个类
article_item["title"] = title
article_item["url"] = response.url
article_item["creat_date"] = creat_date
article_item["front_image_url"] = front_image_url
article_item["praise_nums"] = praise_nums
article_item["comment_nums"] = comment_nums
article_item["fav_nums"] = fav_nums
article_item["tags"] = tags
article_item["content"] = content

yield article_item

并反注释setting里面的ITEM_PIPELINES这个字段。


下载图片

想要下载图片,就还需要修改setting里面的ITEM_PIPELINES这个字段为:

1
2
3
4
5
6
7
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')

其实也就是加了一个字段在ITEM_PIPELINES里面。然后IMAGES_URLS_FIELD是告诉Scrapy你要下载的图片链接,并保存到指定的路径IMAGES_STORE里面去。

如果现在运行,理论上来说会把所有的图片都保存到image这个文件夹下面。但是会报错,因为IMAGES_URLS_FIELD这个字段后面的front_image_url默认传进去的会是一个数组,所以要改前面的article_item["front_image_url"] = [front_image_url]

setting里面IMAGE_MIN_HEIGHTIMAGE_MIN_WIDTH这两个字段可以设置保存的图片最小尺寸。

我们还要获取图片保存的一个路径,那么就需要在pipelines.py里面重新写一个class

1
2
3
4
5
6
7
8
from scrapy.pipelines.images import ImagesPipeline

class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
for ok, value in results:
image_file_path = value['path']
item['front_image_path'] = image_file_path
return item

然后修改settingScrapy执行我们的pipelines

1
2
3
4
5
6
7
8
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')

将刚刚的scrapy.pipelines.images.ImagesPipeline注释掉,改为自己写的。


生成URL的MD5

这个相对来说比较简单,先在ArticleSpider下面新建一个utils包,然后新建一个common.py文件目录结构如下

里面写入:

1
2
3
4
5
6
7
8
import hashlib

def get_md5(url):
if isinstance(url, str):
url = url.encode('utf8')
m = hashlib.md5()
m.update(url)
return m.hexdigest()

然后在jobbole.py文件里面导入这个函数,再在parse_detail里面新增一行:

1
article_item["url_object_id"] = get_md5(response.url)

url_object_id这个字段之前就已经加在了items.py文件中了。


保存Json格式文件

这个也是通过pipelines.py文件里面新添一个class,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from scrapy.exporters import JsonItemExporter

class JsonExporterPipeline(object):
# 调用Scrapy提供的json export到处json文件
def __init__(self):
self.file = open('articleexport.json', 'wb')
self.exporter = JsonItemExporter(self.file, encoding='utf8', ensure_ascii=False)
self.exporter.start_exporting()

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

然后同样要在settings里面ITEM_PIPELINES

1
2
3
ITEM_PIPELINES = {
'ArticleSpider.pipelines.JsonExporterPipeline': 2,
}

ITEM_PIPELINES里面后面的数字表示下面这些类的处理顺序。


保存数据到数据库

安装MySQL数据库服务,创建数据库就不多说了,可以使用navicat这个软件来链接数据库。
数据库叫articlespider,创建的表jobbole_article如下:

还是同样,处理数据这一块都放在pipelines.py文件里面,在里面写一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import MySQLdb

class MysqlPipeline(object):
# 采用同步的机制操作数据库
def __init__(self):
self.conn = MySQLdb.connect('地址', '用户名', '密码', '数据库名', charset='utf8', use_unicode=True)
self.cursor = self.conn.cursor()

def process_item(self, item, spider):
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)
"""
self.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']))
self.conn.commit()
return item

但是这样的一种写法是一种同步机制,就是同步的插入数据库。这样就会产生一个问题,当数据库越来越大的时候,插入数据就会越来越慢,程序就回卡在excute这一步,所以可以用Scrapy的一种异步机制,来操作数据库,同样是写在pipelines.py里面的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from twisted.enterprise import adbapi
import MySQLdb
import MySQLdb.cursors

class MysqlTwistedPipeline(object):
# 异步操作插入数据库
# 这个方法会将setting传进来
def __init__(self, dbpool):
self.dbpool = dbpool

# 这个方法可以调用`settings`里面的参数,固定名称
@classmethod
def from_settings(cls, settings):
dbparms = dict(
host = settings['MYSQL_HOST'],
user = settings['MYSQL_USER'],
passwd = settings['MYSQL_PASSWORD'],
db = settings['MYSQL_DBNAME'],
charset = 'utf8',
cursorclass = MySQLdb.cursors.DictCursor,
use_unicode = True
)

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) # 处理异常
return item

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']))

def handle_error(self, failure):
# 处理异步插入的异常
print(failure)

然后在settings最后添加字段:

1
2
3
4
MYSQL_HOST = "地址"
MYSQL_DBNAME = "数据库名"
MYSQL_USER = "用户名"
MYSQL_PASSWORD = "密码"

上面两个任选一个都行,然后一样要配置settingsITEM_PIPELINES,就不赘述了。


更精简的Item写法

以往的写法是将想要提取的字段通过css或者xpath选择器将其选出,然后再存到item实例里面。但是等我们要提取的字段多了以后,维护起来就会是一场噩梦,所以介绍一种新的保存item的工具,叫做ItemLoader

jobbole.py文件的parse_detail方法中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from ArticleSpider.items import ArticleItemLoader

# Itemloader加载item
front_image_url = response.meta.get("front_image_url", "") # 封面图,用get不会抛异常
item_loader = ArticleItemLoader(item=JobboleArticleItem(), response=response)
item_loader.add_css('title', ".entry-header h1::text")
item_loader.add_value('url_object_id', get_md5(response.url))
item_loader.add_value('url', response.url)
item_loader.add_css('create_date', "p.entry-meta-hide-on-mobile::text")
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('comment_nums', "a[href='#article-comment'] span::text")
item_loader.add_css('fav_nums', ".bookmark-btn::text")
item_loader.add_css('tags', "p.entry-meta-hide-on-mobile a::text")
item_loader.add_css('content', "div.entry")

article_item = item_loader.load_item()

这里面的用法看起来也很简单,就是先实例化了一个自定义的类,在下面给出,然后用这个实例的方法为每个字段添加css或者xpath选择器或者直接赋值,但是这里保存进去之后会变成一个list,后面会介绍如何处理成我们想要的值。

并在items.py文件中加入新的类:

1
2
3
4
5
from scrapy.loader import ItemLoader

class ArticleItemLoader(ItemLoader):
# 自定义itemloader
default_output_processor = TakeFirst()

这个类在刚刚的jobbole.py文件中用到。

然后修改items.py文件中JobboleArticleItem这个类,并添加一些新的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import re
import datetime
from scrapy.loader.processors import MapCompose, TakeFirst, Join

class ArticleItemLoader(ItemLoader):
# 自定义itemloader
default_output_processor = TakeFirst()


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(r".*?(\d+).*", value)
if match_re:
nums = int(match_re.group(1))
else:
nums = 0
return nums


def remove_comment_tags(value):
# 去除tags中的 评论 字样
if '评论' in value:
return ''
else:
return value


def return_value(value):
return value


class JobboleArticleItem(scrapy.Item):
title = scrapy.Field(
input_processor=MapCompose(return_value)
)
create_date = scrapy.Field(
input_processor=MapCompose(date_convert)
)
url = scrapy.Field()
url_object_id = scrapy.Field()
front_image_url = scrapy.Field(
output_processor=MapCompose(return_value)
)
front_image_path = scrapy.Field()
praise_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
comment_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
fav_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
tags = scrapy.Field(
input_processor=MapCompose(remove_comment_tags),
output_processor=Join(',')
)
content = scrapy.Field()

刚刚说到,我们只是把字段和选择器匹配了起来,我们还需要对选择器返回的值进行处理,这里就需要给每个字段定义的时候,加上input_processor,这个后面就是表示输入的值会被进行一些什么操作,比方说MapCompose这个函数就是依次执行传进去的函数。

create_date为例,就是将前面删选的值,传进date_convert函数,然后最后保存为一个list,怎么让输出不是list呢,就是刚刚添加的新的类ArticleItemLoader,它继承了ItemLoader重载了default_output_processor这个值,TakeFirst就是取list的第一个值作为输出。

其中tags的输出我们不希望是输出第一个,就用Join函数将所有的标签连在一起,和python自带的join一样。

front_image_url不希望取第一个,而是保持list输出,就用了一个什么都没做的函数覆盖默输出函数。

还有一点就是,要更改pipelines.py中的下载文件类:

1
2
3
4
5
6
7
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
if 'front_image_url' in item:
for ok, value in results:
image_file_path = value['path']
item['front_image_path'] = image_file_path
return item


所有代码

很想贴在这里,可是想一想太长了,就还是放在github上面吧。

到这里下载


后记

与其说是教程,笔记可能更加合适吧。如果大家有什么问题,欢迎留言~~~