scrapy抓取豆瓣电影相关数据
1. 任务分析及说明
目标网站:https://movie.douban.com/tag/#/
抓取豆瓣电影上,中国大陆地区,相关电影数据约1000条;数据包括:电影名称、导演、主演、评分、电影类型、语言、上映时间、短评top20等数据;
1.1 Fiddler抓包要点分析:
请求均为GET请求;拼接后的URL为是https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0
其中,range表示评分区间(0,10表示筛选评分在0-10之间的电影);
tags表示分类类别(电影?剧集?);
第一次请求默认返回20部电影相关信息,start=0;点击加载更多start=20,即每次点击一次加载更多,start增加20;
返回数据为json格式,数据包括电影名称、导演、电影详情的URL等信息;
从json数据中提取电影详情页的URL,访问并抓取详情信息;
抓取电影短评时,只抓取了最前面的20条,并利用//拼接成一个字符串,数据保存为excel形式。
2. 代码逻辑
2.1 项目创建
利用scrapy的基本命令创建项目、爬虫等,在此不细说,直接上命令。
scrapy startproject DoubanMovie # 创建项目
cd DoubanMovie # 进入项目目录
scrapy genspider douban douban.movie.com # 创建爬虫
2.2 明确抓取字段
scrapy爬虫的套路都相似,创建项目后首先明确爬取字段;其次,编写爬虫逻辑;然后,编写数据保存逻辑;最后,做一些修修补补的工作,例如添加请求头啊,注册通道呀等等。
来到items.py文件中,明确要抓取的字段。
# -*- coding: utf-8 -*- import scrapy class DoubanmoviesItem(scrapy.Item): # 电影名称 filmtitle = scrapy.Field() # 电影评分 moviemark = scrapy.Field() # 导演名称 moviedirt = scrapy.Field() # 电影主演 movierole = scrapy.Field() # 电影类型 movietype = scrapy.Field() # 制片地区 moviearea = scrapy.Field() # 语言类型 movielang = scrapy.Field() # 上映时间 moviedate = scrapy.Field() # 剧情简介 moviesyno = scrapy.Field() # 电影短评 moviecoms = scrapy.Field() # # 电影影评 # movierews = scrapy.Field()
2.3 爬虫逻辑
明确抓取字段后,开始到spiders文件夹下的douban.py中编写爬虫逻辑。豆瓣电影返回的数据为json格式,对json格式的数据进行解析,从中提取到电影详情页的url,访问并从中提取详细信息。
# -*- coding: utf-8 -*- import re import json import scrapy from DoubanMovies.items import DoubanmoviesItem class DoubanSpider(scrapy.Spider): name = 'douban' allowed_domains = ['movie.douban.com'] # start_urls = ['http://movie.douban.com/'] start = 0 # 指定参数 formdata = { 'sort': 'U', 'range': '0, 10', 'tags': '电影', 'start': '0', 'countries': '中国大陆' # 这里只抓取中国大陆地区,其他地区可做相应修改 } base_url = 'https://movie.douban.com/j/new_search_subjects' def start_requests(self): # 构造初始请求url url = self.base_url + '?' + 'sort={}&range={}&tags={}&start={}&countries={}'.format( self.formdata['sort'], self.formdata['range'], self.formdata['tags'], self.formdata['start'], self.formdata['countries'] ) # 发起请求 yield scrapy.Request( url=url, callback=self.parse, meta={'formdata': self.formdata} ) def parse(self, response): """ 豆瓣默认返回json格式的数据 :param response: :return: """ formdata = response.meta['formdata'] # 将json格式的数据转化为字典 data_list = json.loads(response.body.decode())['data'] # 数据解析 for data in data_list: # 从json数据中解析基本信息 item = DoubanmoviesItem() item['filmtitle'] = data['title'] item['moviemark'] = data['rate'] item['moviedirt'] = ' '.join(data['directors']) item['movierole'] = ' '.join(data['casts']) # 拿到详情页链接,获取影评等信息 detail_url = data['url'] yield scrapy.Request( url=detail_url, callback=self.parse_detail, meta={'item': item, 'formdata': formdata} # 传入item到parse_detail,继续解析数据 ) if not self.start == 1000: # 抓取1020条数据 self.start += 20 formdata = self.formdata formdata['start'] = str(self.start) url = self.base_url + '?' + 'sort={}&range={}&tags={}&start={}&countries={}'.format( formdata['sort'], formdata['range'], formdata['tags'], formdata['start'], formdata['countries']) yield scrapy.Request( url=url, callback=self.parse, meta={'formdata': formdata} ) def parse_detail(self, response): """ 从详情页解析其他信息 :param response: :return: """ formdata = response.meta['formdata'] item = response.meta['item'] item['movietype'] = '/'.join(response.xpath("//div[@id='info']/span[@property='v:genre']/text()").extract()) item['moviearea'] = formdata['countries'] item['movielang'] = ''.join(re.findall('<span class="pl">语言:</span>(.*?)<br/>', response.body.decode())) item['moviedate'] = '/'.join(response.xpath("//div[@id='info']/span[@property='v:initialReleaseDate']/text()").extract()) item['moviesyno'] = response.xpath("//div[@id='link-report']/span[1]/text()").extract_first().strip() # 新页面解析电影短评 coms_url = response.xpath("//div[@id='comments-section']/div[1]/h2/span/a/@href").extract_first() yield scrapy.Request( url=coms_url, callback=self.parse_coms, # 在parse_coms中提取电影短评,这里只提取前20 meta={'item': item} ) def parse_coms(self, response): """ 解析电影短评top20,将20条短评以//拼接成一个字符串 :param response: :return: """ item = response.meta['item'] # 提取短评top20 coms_list = response.xpath("//div[@id='comments']/div[@class='comment-item']/div[@class='comment']/p/span/text()").extract() item['moviecoms'] = '//'.join(coms_list) yield item
2.4 数据保存
编写完爬虫逻辑后,来到pipelines.py文件中编写保存数据逻辑。这里将数据保存为excel格式。
# -*- coding: utf-8 -*- from openpyxl import Workbook class DoubanmoviesPipeline(object): def __init__(self): # 创建excel表格保存数据 self.workbook = Workbook() self.booksheet = self.workbook.active self.booksheet.append(['电影名称', '评分', '导演', '主演', '电影类型', '制片地区', '语言类型', '上映时间', '剧情简介', '短评(top20)']) def process_item(self, item, spider): DATA = [ item['filmtitle'], item['moviemark'], item['moviedirt'], item['movierole'], item['movietype'], item['moviearea'], item['movielang'], item['moviedate'], item['moviesyno'], item['moviecoms']] self.booksheet.append(DATA) self.workbook.save('./results.xls') return item
2.5 其他
1. 通道注册,包括下载中间件,pipelines等的注册,还有不遵循爬虫协议
2. 延时处理,在settings.py文件中添加
DOWNLOAD_DELAY = 5 # 每个请求延迟5秒
3. 添加请求头
在下载中间件(middlewares.py)中给每个请求添加请求头
# -*- coding: utf-8 -*- from DoubanMovies.settings import USER_AGENTS as ua import random class DoubanmoviesDownloaderMiddleware(object): def process_request(self, request, spider): """ 给每一个请求随机分配一个代理 :param request: :param spider: :return: """ user_agent = random.choice(ua) request.headers['User-Agent'] = user_agent
4. 将运行命令写在main.py文件中
from scrapy import cmdline cmdline.execute('scrapy crawl douban'.split())