本课只针对python3环境下的Scrapy版本(即scrapy1.3+)
选取什么网站来爬取呢?
对于歪果人,上手练scrapy爬虫的网站一般是官方练手网站 http://quotes.toscrape.com
我们中国人,当然是用豆瓣Top250啦!https://movie.douban.com/top250
第一步,搭建准备
- 为了创造一个足够干净的环境来运行scrapy,使用virtualenv是不错的选择。
>>> mkdir douban250 && cd douban250
>>> virtualenv -p python3.5 doubanenv
首先要保证已经安装有virtualenv和python3.x版本,上面命令为创建python3.5环境的virtualenv
virtualenv教程:廖雪峰python教程-virtualenv
- 开启virtualenv,并安装scrapy
>>> source doubanenv/bin/activate
>>> pip install scrapy
- 使用scrapy初始化项目一个项目,比如我们命名为douban_crawler
>>> scrapy startproject douban_crawler
这时生成了一个目录结构
douban_crawler/
douban.cfg
douban_crawler/
__init__.py
items.py
middlewares.py
piplines.py
setting.py
并且命令行会给你一个提示:
You can start your first spider with:
cd douban_crawler
scrapy genspider example example.com
- 按提示执行这两步吧!
>>> cd douban_crawler
>>> scrapy genspider douban movie.douban.com/top250
genspider后目录结构中增加了spider目录
douban_crawler/
douban.cfg
douban_crawler/
spiders/
__init__.py
douban.py
__init__.py
items.py
middlewares.py
piplines.py
setting.py
在pycharm中设置好项目
- 首先,在pycharm中打开douban_crawler/
- 然后设置pycharm的虚拟环境:
Perference > Project:douban_crawler > Project Interpreter
点击设置图标> Add Local > existing environment;把预置的python解析器,切换到刚刚创立的virtualenv下的doubanenv > bin > python3.5
开始爬取
准备工作完成,可以开始爬取了!
打开spiders/目录下的douban.py文件
# douban_crawler/ > spiders/ > douban.py
import scrapy
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com/top250']
start_urls = ['http://movie.douban.com/top250/']
def parse(self, response):
pass
start_urls就是我们需要爬取的网址啦!
把start_urls中的http://movie.douban.com/top250/
改成https://movie.douban.com/top250/
接下来我们将改写parse()函数,进行解析。
解析豆瓣250条目
使用chrome或firefox浏览器打开https://movie.douban.com/top250/
使用右键菜单中的检查(inspect)分析元素,可以看出:
'.item'包裹了一个个词条
每一个词条下面
'.pic a img'的src属性包含了封面图片地址
'.info .hd a'的src属性包含了豆瓣链接
'.info .hd a .title'中的文字包含了标题,因为每个电影会有多个别名,我们只用取第一个标题就行了。
'.info .bd .star .rating_num'包含了分数
'.info .bd .quote span.inq'包含了短评
另外导演、年代、主演、简介等信息需要点击进入条目的才能爬取,我们先爬取以上五条信息吧!
按照刚刚的解析,填写parse()函数
# douban_crawler/ > spiders/ > douban.py
# -*- coding: utf-8 -*-
import scrapy
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com/top250']
start_urls = ['https://movie.douban.com/top250/']
def parse(self, response):
items = response.css('.item')
for item in items:
yield {
'cover_pic': item.css('.pic a img::attr(src)').extract_first(),
'link': item.css('.info .hd a::attr(href)').extract_first(),
'title': item.css('.info .hd a .title::text').extract_first(),
'rating': item.css('.info .bd .star .rating_num::text').extract_first(),
'quote': item.css('.info .bd .quote span.inq::text').extract_first()
}
.css()运行类似于jquery或pyquery的解析器,但它可以用::text或::attr(href)来直接获取属性或文字,应当说比jquery解析器还要更方便。
当然,.css只能返回一个对象,而需要具体的文字或属性,则需要.extract()或.extract_first()
其中.extract()返回所有符合条件的文字或属性数组,而.extract_first()只返回查询到的第一条文字或属性。
在shell里验证解析语句是否正确
这里我们需要给大家一个窍门,就是先用shell验证刚刚写的css对不对
在pycharm窗口左下方打开terminal
命令行输入:
>>> scrapy shell https://movie.douban.com/top250
会出来一堆的返回信息,最后会出来一堆提示
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x10d276f28>
[s] item {}
[s] request <GET https://movie.douban.com/top250>
[s] response <403 https://movie.douban.com/top250>
[s] settings <scrapy.settings.Settings object at 0x10e543128>
[s] spider <DefaultSpider 'default' at 0x10fa99080>
[s] Useful shortcuts:
[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s] fetch(req) Fetch a scrapy.Request and update local objects
[s] shelp() Shell help (print this help)
[s] view(response) View response in a browser
我们看到response一项
——什么?返回的403错误?
原来我们爬取豆瓣没有设置User-Agent请求头,而豆瓣不接受无请求头的Get请求,最终返回了403错误。
这时赶紧在settings.py里面加入一行
# spider_crawler/ > settings.py
USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
保存文件后,重新运行
>>> scrapy shell https://movie.douban.com/top250
提示:
[s] response <200 https://movie.douban.com/top250>
这时就可以开始检验刚刚的解析语句能不能获得想要的结果了:
response.css('.item')
返回了一个<Selector>对象数组,我们取其中的第一个selector
>>> items = response.css('.item')
>>> item = item[0]
#把cover_pic对应的解析语句拷贝过来
>>> item.css('.pic a img::attr(src)').extract_first()
返回
'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg'
即证明解析语句正确,其它的四项可以一一验证
>>> item.css('.pic a img::attr(src)').extract_first()
'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg'
>>> item.css('.info .hd a::attr(href)').extract_first()
'https://movie.douban.com/subject/1292052/'
>>> item.css('.info .hd a .title::text').extract_first()
'肖申克的救赎'
>>> item.css('.info .bd .star .rating_num::text').extract_first()
'9.6'
>>> item.css('.info .bd .quote span.inq::text').extract_first()
'希望让人自由。'
这时候用exit()退出shell
再运行爬虫
>>> scrapy crawl douban
就可以看到解析后的数据输出了!
翻页爬取全部250条数据
刚刚我们初步爬取了一下,但这一页只显示25条,如何通过翻页爬到全部250条呢?
通过chrome浏览器的“检查”功能,我们找到豆瓣页面上的“下页”所对应的链接:
response.css('.paginator .next a::attr(href)')
现在我们改写一个douban.py
# douban_crawler/ > spiders/ > douban.py
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com/top250']
start_urls = ['https://movie.douban.com/top250']
def parse(self, response):
items = response.css('.item')
for item in items:
yield {
'cover_pic': item.css('.pic a img::attr(src)').extract_first(),
'link': item.css('.info .hd a::attr(href)').extract_first(),
'title': item.css('.info .hd a .title::text').extract_first(),
'rating': item.css('.info .bd .star .rating_num::text').extract_first(),
'quote': item.css('.info .bd .quote span.inq::text').extract_first()
}
next_page = response.css('.paginator .next a::attr(href)').extract_first()
if next_page:
next_page_real = response.urljoin(next_page)
yield scrapy.Request(next_page_real, callback=self.parse,dont_filter=True)
以上通过response.urljoin()返回了拼接后的真实url
然后通过一个递归返回Request对象,遍历了所有的页面。
注意!爬豆爬一定要加入dont_filter=True选项,因为scrapy只要解析到网站的Url有'filter=',就会自动进行过滤处理,把处理结果分配到相应的类别,但偏偏豆瓣url里面的filter为空不需要分配,所以一定要关掉这个选项。
这时再运行一次爬虫
>>> scrapy crawl douban
250条数据已经爬取出来啦!
存储数据到文件
很简单,运行爬虫时加个-o就输出啦!
>>> scrapy crawl douban -o douban.csv
于是你看到当前目录下多了一个.csv文件,存储的正是我们想要的结果!
你也可以用 .json .xml .pickle .jl 等数据类型存储,scrapy也是支持的!
利用Items把爬到的数据结构化
Scrapy的Item功能很类似于Django或其它mvc框架中的model作用,即把数据转化成固定结构,这样才能便出保存和展示。
我们打开 items.py文件,如下定义一个名为DoubanItem的数据类型。
# douban_clawler > items.py
import scrapy
class DoubanItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
rating = scrapy.Field()
cover_pic = scrapy.Field()
quote = scrapy.Field()
有了DoubanItem后,就可以改造douban.py里面的parse函数,使爬取的信息全部转化为Item形式啦
# -*- coding: utf-8 -*-
import scrapy
from douban_crawler.items import DoubanItem
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com/top250']
start_urls = ['https://movie.douban.com/top250']
def parse(self, response):
items = response.css('.item')
for item in items:
yield DoubanItem({
'cover_pic': item.css('.pic a img::attr(src)').extract_first(),
'link': item.css('.info .hd a::attr(href)').extract_first(),
'title': item.css('.info .hd a .title::text').extract_first(),
'rating': item.css('.info .bd .star .rating_num::text').extract_first(),
'quote': item.css('.info .bd .quote span.inq::text').extract_first()
})
next_page = response.css('.paginator .next a::attr(href)').extract_first()
if next_page:
next_page_real = response.urljoin(next_page)
yield scrapy.Request(next_page_real, callback=self.parse,dont_filter=True)
非常简单,只修改了两行:
- 引入DoubanItem
- 原来yield的一个dict格式,现在直接在DoubanItem中传入dict就可以把dict转化成DoubanItem对象了!
现在你可以scrapy crawl douban
再试一次爬取,看是不是已经转换成了DoubanItem形式了?
存储数据到MongoDB
有了DoubanItem数据结构,我们就可以保存进MongoDB啦!
保存到MongoDB,我们需要用到pipline组件,没错,就是上面那一堆文件中的piplines.py
之前我们确保两件事:
- mongodb服务已经开启。如果没有开启请sudo mongod开启本地。
- pymongo包已安装。如果没有安装请pip install pymongo
使用pipline,请记住四个字!
启!
(开启爬虫)对应于open_spider,在spider开启时调用
在这里面启动mongodb
承!
(承接爬取任务)对应于from_clawler,它有几个特点:
- 它是类对象,所以必须加上@classmethod。
- 只要有这个函数,它就一定会被调用。
- 它必须返回我们Pipline本身对象的实例。
- 它有进入Scrapy所有核心组件的能力,也就是说可以通过它访问到settings对象。
转!
(转换对象)对应于process_item,它有几个特点:
- 必须返回Item对象或raise DropItem()错误
- 在这个函数内将传入爬取到的item并进行其它操作。
合!
(闭合爬虫)于应于close_spider,在spider关闭是调用
我们可以在这个函数内关闭MongoDB
有了以上知道,我们上代码!
# douban_crawler > piplines.py
import pymongo
class DoubanCrawlerPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri = crawler.settings.get('MONGO_URI'),
mongo_db = crawler.settings.get('MONGO_DB')
)
def process_item(self, item, spider):
self.db['douban250'].insert_one(dict(item))
return item
def close_spider(self, spider):
self.client.close()
同时在setting中添加MONGODB的配置
MONGO_URI = "localhost"
MONGO_DB = "douban"
还有非常重要的一步!
在setting中打开pipline的注释!!
ITEM_PIPELINES = {
'douban_crawler.pipelines.DoubanCrawlerPipeline': 300,
}
现在打开crawler
scrapy crawl douban
爬到的信息已经保存到数据库了!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。