[In Action] - Scrapy 爬虫开发实战与开发技巧
摘要:关于Scrapy 爬虫开发实战与开发技巧,重点关注scrapy shell设置ua头,custom_settings 。
1 概述
本节我们将在 Conda 环境下使用 Pycharm 编写一个简单的爬虫,我们的目标是 豆瓣电影TOP250。在这篇文章中我将和大家分享几个我在爬虫编写中的思路、流程以及技巧。
Pycharm 中开发环境的加载如下图:
2 明确目标
首先明确我要抓取的目标数据是什么?以及我的目标数据分布在哪?下面图例中圈出了我需要解析的数据:
解析内容依据网页结构可以分为如下几块:
- 排序
- 电影中文名、其他名字
- 导演、主演、年代、地区、类别标签
- 评分、参与评分人数
- 电影描述
这是我们根据一级页面可以获取的数据,其实在二级页面中我们可以获得更多的电影相关数据
但是本章将不深入二级页面,因为他的方法在我们下面的内容都会涉及到。
另外e 在目标明确中的另一个问题是所有的数据都在哪?豆瓣电影250作为一个榜单显然数据只有250条每个页面有25条,那它们平均分布在10个页面内,也就是说我的爬虫只要翻完10个页面并正确解析数据就能达成目标。
3 初探页面访问
本次爬虫类我们将继承使用 CrawlSpider,如果你尚未熟悉它的用法可以参考历史文章:
框架的搭建我们第一步是先看看使用 Scrapy 能不能直接获取正确的页面,因为可能目标网站的反爬虫策略使得我们无法获取网页数据,因此我们可以这样先试试:
scrapy shell https://movie.douban.com/top250
访问结果:
网页状态码是403表示服务器拒绝访问,也就是我们无法通过不加设置的方法进行获取数据,我们看看放回的网页长什么样:
view(response)
自动打开网页:
最简单处理的方法是查看真正的浏览器方法时的行为,我们照样子模仿。
打开浏览器在目标页面上按 F12 切换 Tab 到 Network
按 F5 刷新页面,找到我们的目标页面 top250 点击它,在右边的窗口下拉找到 Request Headers,这个表示我们发起请求是的头部标识。
我们可以首先尝试添加 Host 和 User-Agent 到我们的爬虫请求中,前者一般用于图片反盗链后者由于表示访问者身份。我们可以这样做:
添加UA:
scrapy shell -s USER_AGENT="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "https://movie.douban.com/top250"
查看请求状态(200):
查看请求页面:
到这里我们已经能够访问页面了。
另外一种可选的方法:
先启动 shell
scrapy shell
再请求数据:
>>> from scrapy import Request
>>> req = Request('https://movie.douban.com/top250', headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.361"})
>>> fetch(req)
4 爬虫框架搭建
做好上面的准备,我们可以用以前的 CrawlSpider 的代码初步写个框架:
# -*- coding: utf-8 -*-
# filename: douban.py
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = 'movie_douban'
allowed_domains = ['douban.com']
start_urls = ["https://movie.douban.com/top250"]
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
}
custom_settings = {
'DEFAULT_REQUEST_HEADERS': headers,
}
rules = (
Rule(LinkExtractor(allow=()),
follow=True,
callback='parse_item',
),
)
def parse_item(self, response):
pass
这样我们就有了基本框架,接下来看我们需要做的是填充它:
- 页面跟随规则
- 数据解析规则
5 页面跟随规则
在爬虫中 LinkExtractor 的规则编写关系到你能不能爬取完整的数据,这里们有个技巧用去调试链接的跟随规则,添加 process_links 回调函数,部分代码改写如下:
rules = (
Rule(LinkExtractor(allow=()),
follow=True,
callback='parse_item',
process_links='process_links',
),
)
@staticmethod
def process_links(links):
for link in links:
yield link
到这里我们可以试着通过 Pycharm 的 Debug 模式运行 下面的 run.py 文件对爬虫进行调试。
# -*- coding: utf-8 -*-
from scrapy import cmdline
cmdline.execute("scrapy runspider douban.py".split())
在上面由于我们在 LinkExtractor 的 follow 设为空意味着提取当前页面的所有链接进行 follow,但是我们只需要翻页那该怎么做呢?使用正则!
我们先复制第2页、第3页的链接看看该怎么写(直接在2上复制链接,不要看网页源码)
https://movie.douban.com/top250?start=25&filter=
https://movie.douban.com/top250?start=50&filter=
规律很明显用 start=数字 控制当前页出现的第一个电影序号,匹配多个数字在正则中可以写成 \d+,因此规则编写如下:
rules = (
Rule(LinkExtractor(allow=('/top250\?start=\d+&filter=',)),
follow=True,
callback='parse_item',
process_links='process_links',
),
)
由于 问号 在正则中是表示重复前面内容的0次或1次,要匹配问号本身需要转义。
另一种方法:
LinkExtractor 还有两个参数是 restrict_xpaths 、restrict_css 表示在 xpath 、css 匹配区域提取指定链接,因此我们可以先指定链接提取区域为翻页区块,再提取全部链接:
xpath如下:
//div[@class="paginator"]
具体 Rule:
rules = (
Rule(LinkExtractor(allow=(),
restrict_xpaths=('//div[@class="paginator"]',)),
follow=True,
callback='parse_item',
process_links='process_links',
),
)
css 如下:
div.paginator
具体 Rule:
rules = (
Rule(LinkExtractor(allow=(),
restrict_css=('div.paginator',)),
follow=True,
callback='parse_item',
process_links='process_links',
),
)
6 数据解析规则
做好链接跟随规则后就可以正式开始我们数据的解析了。
一般我们可以在回调函数中加载 Shell 进行数据解析调试:
def parse_item(self, response):
from scrapy.shell import inspect_response
inspect_response(response, self)
在上面中已经提到数据解析分为如下几块:
- 排序
- 电影中文名、其他名字
- 导演、主演、年代、地区、类别标签
- 评分、参与评分人数
- 电影Quote
在第三块中,这些内容其实是混杂在一起的只能通过字符串查找、正则匹配等方法格式化数据,最好的方法是进入该条目的二级页面从二级页面中在2解析响应的数据。下面我通过了使用 ItemLoader 的方法对数据进行解析清洗。
# -*- coding: utf-8 -*-
from scrapy import Item, Field
from scrapy.loader import ItemLoader
from w3lib.html import remove_tags
from scrapy.loader.processors import Join, MapCompose, TakeFirst
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MovieItem(Item):
title = Field()
other = Field()
director = Field() # 未解析
starring = Field() # 未解析
year = Field()
country = Field() # 未解析
category = Field()
rating = Field()
num = Field()
quote = Field()
class MovieLoader(ItemLoader):
"""
数据清洗
"""
default_output_processor = TakeFirst()
default_input_processor = MapCompose(unicode.strip)
title_in = MapCompose(lambda x: x.replace(u'\xa0/\xa0', ''))
title_out = Join(' / ')
other_in = MapCompose(lambda x: x[3:], lambda x: x.replace(' '*2, ' '))
num_in = MapCompose(lambda x: x[:-3])
class MySpider(CrawlSpider):
name = 'movie_douban'
allowed_domains = ['douban.com']
start_urls = ["https://movie.douban.com/top250"]
headers = {
'Host': 'movie.douban.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
}
custom_settings = {
'FEED_EXPORT_ENCODING': 'utf-8',
'FEED_FORMAT': 'jsonline',
'FEED_EXPORT_INDENT': 4,
'DEFAULT_REQUEST_HEADERS': headers,
'HTTPCACHE_ENABLED': True,
}
rules = (
Rule(LinkExtractor(allow=(),
restrict_css=('div.paginator',)),
follow=True,
callback='parse_item',
),
)
def parse_item(self, response):
movies = response.css('div.item')
for movie in movies:
loader = MovieLoader(item=MovieItem(), selector=movie)
loader.add_css('title', 'span.title::text')
loader.add_css('other', 'span.other::text')
loader.add_xpath('year', './/div[@class="bd"]/p[1]/text()', Join(), remove_tags,
re='\d{4}')
loader.add_xpath('category', './/div[@class="bd"]/p[1]/text()', Join(), remove_tags,
lambda x: x[x.rfind('/')+1:])
loader.add_css('rating', 'span.rating_num::text')
loader.add_xpath('num', './/div[@class="star"]/span[4]/text()')
loader.add_xpath('quote', './/span[@class="inq"]/text()')
yield loader.load_item()
更详细的介绍看历史文章:
我们的成品数据大概长这样,当然你也可以使用 csv 格式导出。
这样的数据可以使用 json 库进行读取:
import json
with open('movies.json', 'r') as f:
data = json.loads(f.read())
print type(data)
数据解析这一块还是需要多多练习才能如鱼得水,使用 ItemLoader 作为简单的数据清洗工作。在模块化的 Scrapy 项目中使用 Pipeline 做数据的验证、持久化都是可以的。如果我们只是想简单获取数据建议直接使用单文件爬虫,这样更佳方便。
如果你有什么问题欢迎评论交流。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。