一、框架详解
Scrapy是由Twisted写的一个受欢迎的python事件驱动网络框架,它使用的是非阻塞的异步处理。
【1】内部各组件的作用
**ScrapyEngine(scrapy引擎):**是用来控制整个系统的数据处理流程,并进行事务处理的触发。
**Scheduler(调度器):**用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回它。它就像是一个URL的优先队列,由它来决定下一个要抓取的网址是什么,同时在这里会去除重复的网址。
**Downloader(下载器):**用于下载网页内容,并将网页内容返回给爬虫(Spiders)[Scrapy下载器是建立在Twisted这个高效的异步模形上的]
**Spiders(爬虫):**爬虫主要是干活的,用于从特定网页中爬取自己需要的信息,即所谓的实体(item),也可以从中提取URL,让Scrapy继续爬取下一个页面。
**Pipeline(项目管道):**负责处理爬虫从网页中爬取的实体,主要功能就是持久化实体,验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被送到项目管道,并经过几个特定的次序处理数据。
**Downloader Middlewares(下载器中间件):**位于scrapy引擎和下载器之间的框架,主要是处理scrapy引擎与下载器之间的请求及响应。设置代理ip和用户代理可以在这里设置。
**Spider Middlewares(爬虫中间件):**位于scrapy引擎和爬虫之间的框架,主要工作是处理爬虫的响应输入和请求输出。
**Scheduler Middlewares(调度中间件):**位于scrapy引擎和调度器之间的框架,主要是处理从scrapy引擎发送到调度器的请求和响应。
【2】Scrapy运行流程
- 引擎从调度器中取出一个URL用于接下来的抓取
- 引擎把URL封装成一个请求(Request)传给下载器
- 下载器把资源下载下来,并封装成一个响应(Response)
- 爬虫解析Response
- 解析出来的就是实体(item),则交给项目管道(Pipeline)进行进一步处理
- 解析出来的是链接(URL),则把URL交给调度器等待下一步的抓取
二、项目案例
基于Scrapy 框架影视信息采集与分析
项目介绍
为了充分利用网上大数据资源,让用户能够方便利用影视信息,采用基于 Scrapy 框架的爬虫技术,开
发
了检索电影信息的搜索引擎。对豆瓣网站的影视信息进行爬取,以方便用户准确获取最新的电影信息。
项目代码
以“豆瓣电影”为爬取目 标,爬取网站中的影视信息。主要包括网站排名 “ Top250 ”和喜剧、动作类电影
的电影名称、电影评分、电影导演, 电影上映时间以及电影评语。
【1】创建工程
scrapy startproject Douban
【2】创建爬虫程序
cd Douban/
scrapy genspider douban 'douban.com'
爬虫框架会自动创建目录和文件
【3】确定爬取目标《item.py》
class DoubanItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()#电影名字
score = scrapy.Field()#电影评分
quote = scrapy.Field()#电影评语
director =scrapy.Field()#电影导演
release_date = scrapy.Field() #上映时间
comment_num = scrapy.Field()#评论数
image_url = scrapy.Field()#电影图片的url地址
detail_url = scrapy.Field() #电影详情页信息
image_path =scrapy.Field() #下载的封面本地存储位置
【4】更改setting配置文件,
1、添加Useragent、禁止爬虫协议
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'Douban (+http://www.yourdomain.com)'
#设置随机用户代理
from fake_useragent import UserAgent
ua =UserAgent()
USER_AGENT = ua.random
# Obey robots.txt rules
# ROBOTSTXT_OBEY = True
ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
# 'Douban.pipelines.DoubanPipeline': 300,
'scrapy.pipelines.images.ImagesPipeline': 1,
'scrapy.pipelines.files.FilesPipeline': 2,
'DouBan.pipelines.MyImagesPipeline': 2,
'DouBan.pipelines.DoubanPipeline': 300,
'DouBan.pipelines.JsonWriterPipeline': 200, # 数字越小, 越先执行;
'DouBan.pipelines.AddScoreNum': 100, # 处理爬去的数据, 处理完成后才保存;
'DouBan.pipelines.MysqlPipeline': 200, # 处理爬去的数据, 处理完成后才保存;
}
FILES_STORE ='/tmp/files'
IMAGES_STORE = '/tmp/images'
IMAGES_EXPIRES = 30
# 图片缩略图
IMAGES_THUMBS = {
'small': (250, 250),
'big': (270, 270),
}
# 图片过滤器,最小高度和宽度
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
2、设置pipeline,
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,这些Item Pipeline组件按定义的顺序处理Item。
每个Item Pipeline都是实现了简单方法的Python类,比如决定此Item是丢弃而存储。以下是item pipeline的一些典型应用:
1.验证爬取的数据(检查item包含某些字段,比如说name字段)
2.查重(并丢弃)
3.将爬取结果保存到文件或者数据库中
我在这里通过对爬取到的数据进行处理(加1),之后存入mysql数据库,获取图片并进行下载
import json
import pymysql
import scrapy
from scrapy.exceptions import DropItem
class DoubanPipeline(object):
def process_item(self, item, spider):
return item
class AddScoreNum(object):
"""在原有的评分上加一"""
def process_item(self,item,spider):
if item['score']:
score = float(item['score'])
item['score'] = str(score + 1)
return item
else:
raise Exception("没有爬去到score")
class JsonWriterPipeline(object):
"""爬虫之前打开文件对象, 爬虫之后, 关闭文件对象"""
def open_spider(self, spider):
self.file = open('douban.json', 'w')
def process_item(self, item, spider):
# dict(item): 将item对象转成字典
# json.dumps: 将字典序列化成json字符串;
# indent=4: 存储是缩进为4;
# ensure_ascii=False: 解决中文乱码问题
line = json.dumps(dict(item), indent=4, ensure_ascii=False)
self.file.write(line)
return item
def close_spider(self, spider):
self.file.close()
class MysqlPipeline(object):
"""编写MySQL存储插件"""
def open_spider(self, spider):
# 连接数据库
self.connect = pymysql.connect(
host='127.0.0.1', # 数据库地址
port=3306, # 数据库端口
db='DoubanProject', # 数据库名
user='root', # 数据库用户名
passwd='westos', # 数据库密码
charset='utf8', # 编码方式
use_unicode=True,
autocommit=True
)
# 通过cursor执行增删查改--游标
self.cursor = self.connect.cursor()
self.cursor.execute("create table if not exists douBanTop("
"title varchar(50) unique, "
"score float , "
"quote varchar(100), "
"director varchar(100), "
"comment_num int, "
"release_date varchar(10));")
def process_item(self, item, spider):
insert_sqli = "insert into douBanTop(title, score, quote,director) values ('%s', '%s', '%s', '%s')" % (
item['title'], item['score'], item['quote'], item['director'],)
print(insert_sqli)
try:
self.cursor.execute(insert_sqli)
# 提交sql语句
self.connect.commit()
except Exception as e:
self.connect.rollback()
return item # 必须实现返回
def close_spider(self, spider):
self.connect.commit()
self.cursor.close()
self.connect.close()
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info): # 單個的item對象;
#自动获取请求并下载图片
print("item: ", item)
yield scrapy.Request(item['image_url'])
def item_completed(self, results, item, info):
"""
:param results:
[(True, {'url': 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p1454261925.jpg',
'path': 'full/e9cc62a6d6a0165314b832b1f31a74ca2487547a.jpg',
'checksum': '5d77f59d4d634b795780b2138c1bf572'})]
:param item:
:param info:
:return:
"""
# for result in results:
# print("result: ", result)
image_paths = [x['path'] for isok, x in results if isok]
# print("image_paths: ", image_paths[0])
if not image_paths:
raise DropItem("Item contains no images")
item['image_path'] = image_paths[0]
return item
【5】编写爬虫spider《douban.py》
# -*- coding: utf-8 -*-
import scrapy
from scrapy import Request
from Douban.items import DoubanItem
class DoubanSpider(scrapy.Spider):
#爬虫名字,不可重复
name = 'douban'
allowed_domains = ['douban.com']
start_urls = ['http://douban.com/',
'https://movie.douban.com/top250']
url ='https://movie.douban.com/top250'
def parse(self, response):
item =DoubanItem()
#<ol class="grid_view">
movies = response.xpath('//ol[@class="grid_view"]/li')
for movie in movies:
#电影名称<span class="title">肖申克的救赎</span>
item['title'] = movie.xpath('.//span[@class="title"]/text()').extract()[0]
#电影评分
item['score'] = movie.xpath('.//span[@class="rating_num"]/text()').extract()[0]
#电影评语
quote = movie.xpath('.//span[@class="inq"]/text()').extract()
item['quote'] = quote[0] if quote else ''
#电影导演
info = movie.xpath('.//div[@class="bd"]/p/text()').extract()
director = info[0].split('主演')[0].strip()
item['director'] = director
#电影图片的url地址
item['image_url'] = movie.xpath('.//div[@class="pic"]/a/img/@src').extract()[0]
# 电影详情页信息
item['detail_url'] = movie.xpath('.//div[@class="hd"]//a/@href').extract()[0]
yield item
#<a href="?start=25&filter=">后页></a>
nextLink = response.xpath('.//span[@class="next"]/link/@href').extract() # 返回的是列表
if nextLink:
nextLink = nextLink[0]
print('Next Link: ', nextLink)
yield Request(self.url + nextLink, callback=self.parse)