基于Scrapy爬虫框架的豆瓣电影数据采集
项目介绍
项目简介
- 项目名称:《基于Scrapy爬虫框架的豆瓣电影数据采集》
- 项目领域:大数据采集
项目开发环境
- 编程语言:Python 3.6.5
- 开发IDE:PyCharm
- 使用技术:Scrapy框架
- 数据库:MySQL
- 数据库IDE:SQLYOG
项目需求分析
- 爬虫主程序数据采集模块
- 多元数据持久化模块
- 二进制数据采集模块
- 深度自动采集模块
- 日志系统模块
Scrapy框架
Scrapy框架基础知识
Scrapy框架概念
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。
Scrapy框架功能
爬虫程序基于Scrapy框架创建,借助于Scrapy的item以及pipeline功能实现数据抽取,并将爬取的数据存储到item中。最后通过pipeline对数据进一步处理(存储、打印)。
Scrapy框架安装
安装第三方爬虫离线包
在DOS环境下安装第三方爬虫离线包:
E:\>pip install Twisted-18.4.0-cp36-cp36m-win_amd64.whl
Processing e:\twisted-18.4.0-cp36-cp36m-win_amd64.whl
安装Scrapy爬虫框架
E:\>pip install -U scrapy
Collecting scrapy
查看框架版本号
E:\>pip install scrapy==
Collecting scrapy==
用固定版本号安装
E:\>pip install scrapy==1.5.0
Collecting scrapy==1.5.0
Scrapy框架使用
- 创建一个Scrapy项目
scrapy startproject 项目名
scrapy startproject doubanmov;
- 生成一个爬虫
scrapy genspider 脚本程序名称 域名
scrapy genspider movie "douban.com”
- 提取数据
完善spider,使用xpath等方法 - 保存数据
pipeline管道中保存数据
项目功能实现
爬虫主程序数据采集模块
数据信息采集
- 定义item字段数据(items.py),
- 使用XPATH定位并解析网页源代码,并获取所需要的“电影排名”和“电影名称”数据文本(movie.py)。
- 将采集数据已生成器方式返回至item。
import scrapy
class DoubanmovItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
#排名
rank = scrapy.Field()
#电影名称
title = scrapy.Field()
#电影图片
pic = scrapy.Field()
import scrapy
import re
import math
from doubanmov.items import DoubanmovItem
from doubanmov.logmessage import clogger
class MovieSpider(scrapy.Spider):
name = 'movie'
allowed_domains = ['douban.com']
#爬取首地址
start_urls = ['https://movie.douban.com/top250']
def __init__(self):
self.log = clogger.demo()
def parse(self, response):
#定位采集数据的标签
items_mov = response.xpath('//div[@class="item"]')
#遍历结果
for items in items_mov:
#创建采集对象
mov = DoubanmovItem()
#排名#XPATH解析并赋值
mov['rank'] = items.xpath('div[@class="pic"]/em/text()').extract()
# 名字解析并赋值
mov['title'] = items.xpath('div[@class="info"]/div[@class="hd"]/a/span[@class="title"][1]/text()').extract()
mov['pic'] =items.xpath('div[@class="pic"]/a/img/@src').extract()
#生成生成器输出
yield mov
实现403反爬虫
- 禁用框架自带的浏览器标识(settings.py)
- 重新设置浏览器标识(rotate_useragent.py)
- 使用IP代理器
# 导入random模块
import random
# 导入useragent用户代理模块中的UserAgentMiddleware类
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
# RotateUserAgentMiddleware类,继承 UserAgentMiddleware 父类
# 作用:创建动态代理列表,随机选取列表中的用户代理头部信息,伪装请求。
# 绑定爬虫程序的每一次请求,一并发送到访问网址。
# 发爬虫技术:由于很多网站设置反爬虫技术,禁止爬虫程序直接访问网页,
# 因此需要创建动态代理,将爬虫程序模拟伪装成浏览器进行网页访问。
class RotateUserAgentMiddleware(UserAgentMiddleware):
def __init__(self, user_agent=''):
self.user_agent = user_agent
def process_request(self, request, spider):
#这句话用于随机轮换user-agent
ua = random.choice(self.user_agent_list)
if ua:
# 输出自动轮换的user-agent
print(ua)
request.headers.setdefault('User-Agent', ua)
# the default user_agent_list composes chrome,I E,firefox,Mozilla,opera,netscape
# for more user agent strings,you can find it in http://www.useragentstring.com/pages/useragentstring.php
# 编写头部请求代理列表
user_agent_list = [\
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"\
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",\
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",\
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",\
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",\
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",\
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",\
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",\
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",\
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",\
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",\
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",\
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",\
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",\
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",\
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",\
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",\
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
DOWNLOADER_MIDDLEWARES = {
'doubanmov.middlewares.DoubanmovDownloaderMiddleware': 543,
# 禁用框架自带的浏览器标识
'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
# 设置浏览器标识
'doubanmov.rotate_useragent.RotateUserAgentMiddleware': 400,
}
控制台输出采集结果
- 通过Scrapy框架的pipeline管道将需要的数据通过控制台显示。
- 开放管道输出
ITEM_PIPELINES = {
'doubanmov.pipelines.DoubanmovPipeline': 300,
'doubanmov.pipelinesjson.DoubanmovPipeline': 301,
'doubanmov.pipelinesexc.DoubanmovPipeline': 302,
'doubanmov.pipelinesSQL.DoubanmovPipeline': 303,
'doubanmov.pipelinespic.DoubanmovPipeline': 304,
'doubanmov.pipelinescsv.DoubanmovPipeline': 305,
'doubanmov.pipelinesjson1.DoubanmovPipeline': 301,
}
from doubanmov.logmessage import clogger
class DoubanmovPipeline(object):
def __init__(self):
self.log1 = clogger.demo()
def process_item(self, item, spider):
#管道输出
try:
print('电影排名:{0}'.format(item['rank'][0]))
print('电影名字:<<{0}>>'.format(item['title'][0]))
self.log1.logginfo('输出了<<{0}>>电影'.format(item['title'][0]))
except:
self.log1.loggerror('输出<<{0}>>电影失败'.format(item['title'][0]))
pass
return item
多元数据持久化模块
json文件存储
- 方法一:
- 将item数据存储为列表包字典的模式
- 通过json.jump()将列表写入json文件
import json
from doubanmov.logmessage import clogger
# list1 = []
class DoubanmovPipeline(object):
def __init__(self):
self.list1 = []
self.log = clogger.demo()
def process_item(self, item, spider):
try:
#将item数据存储至字典中
dict1={'排名':item['rank'][0],'名字':item['title'][0]}
# 将字典数据追加至列表中中
self.list1.append(dict1)
# json文件存储
with open('data/jsondate.json',mode='w',encoding='utf-8') as file:
json.dump(self.list1,file,ensure_ascii=False)
#日志存储
self.log.logginfo('向"data/jsondate.json"中存储了<<{0}>>电影'.format(item['title'][0]))
except:
self.log.loggerror('向"data/jsondate.json"中存储<<{0}>>电影失败'.format(item['title'][0]))
return item
- 方法二:
- 将爬取的数据存储为字典类型
- 将字典数据序列化并增加换行功能
import json
from doubanmov.logmessage import clogger
class DoubanmovPipeline(object):
def process_item(self, item, spider):
#获取爬取的数据存成字典模式
dict1 = {'排名': item['rank'][0], '名字': item['title'][0]}
#将列表序列化并增加换行
filejson = json.dumps(dict1, ensure_ascii=False) + '\n'
#用文本方式存储json数据
with open('data/jsondate.json', mode='a', encoding='utf-8') as file:
file.write(filejson)
return item
- 方法三:
利用Scrapy命令存储json文件
#设置存储编码
FEED_EXPORT_ENCODING='UTF-8'
scrapy crawl movie -o myjson.json -t json
csv文件存储
- 方法一:
- 将爬取的数据存储为字典类型
- 将字典数据追加至列表
- 将列表按表头和内容存储数据
import csv
from doubanmov.logmessage import clogger
class DoubanmovPipeline(object):
def __init__(self):
self.list1 = []
self.log = clogger.demo()
def process_item(self, item, spider):
try:
#爬取数据存储为字典类型
dict1={'排名':item['rank'][0],'名字':item['title'][0]}
#将字典数据追加为列表
self.list1.append(dict1)
with open('data/movie.csv',mode='w',encoding='utf-8',newline='') as file:
#csv文件写入
write = csv.DictWriter(file,self.list1[0])
# csv文件头写入
write.writeheader()
# csv文件内容写入
write.writerows(self.list1)
self.log.logginfo('向"data/movie.csv"中存储了<<{0}>>电影'.format(item['title'][0]))
except:
self.log.loggerror('向"data/movie.csv"中存储<<{0}>>电影失败'.format(item['title'][0]))
return item
- 方法二:
利用Scrapy命令存储csv文件
#设置存储编码
FEED_EXPORT_ENCODING='UTF-8'
scrapy crawl movie -o mycsv.csv -t csv
Excel文件存储
- 创建Excel文件
- 获取爬取数据头文件
- 将数据存储为列表包列表的模式
- 按行和列存储数据至Excel文件
import xlwt
list1=[]
from doubanmov.logmessage import clogger
class DoubanmovPipeline(object):
def __init__(self):
# Excel文件创建工作薄
self.workbook = xlwt.Workbook()
#获取单页对象
self.sheet = self.workbook.add_sheet('sheet1')
#添加数据---文件头写
self.header = ['电影排名', '电影名字']
for i in range(len(self.header)):
self.sheet.write(0, i, self.header[i])
self.log = clogger.demo()
def process_item(self, item, spider):
#添加数据---文件内容获取
firlist=[item['rank'][0],item['title'][0]]
list1.append(firlist)
return item
def close_spider(self, spider):
try:
#按行遍历
for i in range(1,len(list1)+1):
#按列遍历
for j in range(len(self.header)):
# 添加数据---文件内容按行列存储
self.sheet.write(i,j,list1[i-1][j])
self.log.logginfo('向"data/movie.xls"中存储了<<{0}>>电影'.format(list1[i - 1][0]))
except:
self.log.loggerror('向"data/movie.xls"中存储<<{0}>>电影失败'.format(list1[i - 1][0]))
#保存Excel文件
self.workbook.save('data/movie.xls')
Excel文件存储
- 连接数据库(主机地址,端口,用户名,密码,数据库名)
- 获取数据库对象
- 执行SQL语句(增删改查)
- 提交事务
import pymysql
from doubanmov.logmessage import clogger
list1=[]
class DoubanmovPipeline(object):
def __init__(self):
# 连接数据库
self.con = pymysql.connect(host='localhost', port=3306, user='root', passwd='root', db='db_movie', )
# 获取游标对象
self.cur = self.con.cursor()
self.log = clogger.demo()
def process_item(self, item, spider):
#执行SQL语句
try:
self.cur.execute('insert into movie values(%d,"%s")' %(int(item["rank"][0]),item["title"][0]))
#提交事务
self.con.commit()
self.log.logginfo('向数据库中存储了<<{0}>>电影'.format(item['title'][0]))
except:
self.con.rollback()
self.log.loggerror('向数据库中存储<<{0}>>电影失败'.format(item['title'][0]))
return item
def close_spider(self,spider):
self.cur.close()
self.con.close()
xml文件存储
利用Scrapy命令存储xml文件
#设置存储编码
FEED_EXPORT_ENCODING='UTF-8'
scrapy crawl movie -o myxml.xml -t xml
二进制数据采集模块
- 通过urllib模块获取海报二进制数据
- 将二进制数据流写入方式将数据写入文件中
import urllib.request as ur
from doubanmov.logmessage import clogger
# import spiders.movie as sm
class DoubanmovPipeline(object):
def __init__(self):
self.log = clogger.demo()
def process_item(self, item, spider):
try:
#通过url获取网页数据
res = ur.urlopen(item['pic'][0])
#读取网页返回数据
res = res.read()
name = item['title'][0] + '.jpg'
#通过基本数据存储海报存储至文件中
with open('data/pic/{0}'.format(name),mode='wb')as file:
file.write(res)
self.log.logginfo('向"data/pic"中下载了<<{0}>>电影'.format(item['title'][0]))
except:
self.log.loggerror('向"data/pic"中下载<<{0}>>电影失败'.format(item['title'][0]))
return item
深度自动采集模块
- 获取下一页数据采集地址(movie.py)
- 判断下一页地址是否为空
- 判断是否继续翻页
- 通过scrapy.request和新地址回调爬去数据函数
#通过第一页数据源代码查找到下一页地址并获取地址
next_di = response.xpath('//div[@class="paginator"]/span[3]/a/@href').extract()
#将地址中的翻页具体数据提取作为日志用
page1 = re.match(r"\?start=(.*?)&",next_di[0])
page = math.floor(int(page1[1])/25)+1
#判断是否为最后一页,是则为空
if next_di:
#判断是否继续翻页
choose = input('是否继续翻页:(y/n)>>>')
if choose.lower()=='y':
self.log.logginfo('选择了继续翻页翻至第{0}页'.format(page))
#拼接下一页的新地址
next_page = 'https://movie.douban.com/top250' + next_di[0]
#回调函数,递归爬虫方法
yield scrapy.Request(next_page,callback=self.parse)
else:
self.log.logginfo('选择了退出')
日志系统模块
- 方法一:
- 通过代用logging模块创建系统日志模块
- 增加日志等级的方法(info,error)
- 通过模块调用至所需的py文件中。
- 调用日志等级方法并存储日志信息
import logging
import os
import time
#创建日志系统模块
class clogger:
a = None
def __init__(self):
#获取logger对象
self.logger = logging.getLogger(__name__)
#设置日志等级
self.logger.setLevel(logging.INFO)
#设置格式化输出
logmat = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s --- %(message)s')
#设置文件路径
if not os.path.exists('log'):
os.mkdir('log')
filepath = os.path.join('log','系统日志'+'.log')
#设置FileHandler对象
filehand = logging.FileHandler(filepath)
#设置级别
filehand.setLevel(logging.INFO)
#设置格式化输出
filehand.setFormatter(logmat)
#添加filehandler对象
self.logger.addHandler(filehand)
#定义静态方法判断是否继续创建类对象
@staticmethod
def demo():
if clogger.a == None:
clogger.a=clogger()
return clogger.a
def logginfo(self,mess):
self.logger.info(mess)
pass
def loggerror(self,mess):
self.logger.error(mess)
pass
#调用创建日志模块
from doubanmov.logmessage import clogger
class DoubanmovPipeline(object):
def __init__(self):
self.log1 = clogger.demo()
def process_item(self, item, spider):
#管道输出
try:
print('电影排名:{0}'.format(item['rank'][0]))
print('电影名字:<<{0}>>'.format(item['title'][0]))
#调用日志模块的方法
self.log1.logginfo('输出了<<{0}>>电影'.format(item['title'][0]))
except:
self.log1.loggerror('输出<<{0}>>电影失败'.format(item['title'][0]))
pass
return item
- 方法二:
利用Scrapy框架的日志系统存储日志(settings.py)
- 日志等级
critical------严重错误
error--------一般错误
warning----警告信息
info----------一般信息
debug------调试信息 - 日志设置
LOG_ENABLE ----默认:TRUE,启用logging
LOG_FILE----------默认:None,在当前目录里创建logging输出文件名
LOG_LEVEL--------默认:DEBUG,log的最低等级
LOG_STDOUT-----默认:FALSE,如果为TRUE,进程将所有标准化输出重定向到log文件中
LOG_ENCODING----默认:utf-8
LOG_ENABLED = False
LOG_FILE = 'log/aaa.log'
LOG_LEVEL = "INFO"
LOG_STDOUT = True