一、项目选择的背景及意义
1.1 项目选择背景
电影作为一种艺术,近年来,已经深入到人们社会生活的方方面面,成为人们生活当中必不可少的一部分。随着世界电影行业的日益发展,电影中运用拍摄技术越来越先进,同时越来越多的电影被展现在大众面前,但是这些电影的拍摄质量和受欢迎度都参差不齐,仅仅通过观看预告片是无法判断一部电影的好坏的。想要看到一部好的电影,我们日常空闲时间想看部电影时却发现不知道看什么,花了半个小时去搜索、筛选,都没有特别满意的。最后可能随便找一部凑和着看,或者直接放弃,或者通过朋友介绍和自己花时间自己去看才能有所了解电影的好坏程度,我们也不能做到把每部电影都看一遍来判断好坏,所以单靠这两条途径我们是很费时费力的,有什么办法能够在较短时间内很清晰的筛选出高质量的电影,从而做到有选择的去看。通过这学期学习的网络爬虫技术,就可以很好的解决刚刚的问题,所以我设计了一个爬取豆瓣Top250电影排行榜数据的项目。
1.2 项目研究的具体问题和范围
1.2.1 项目研究的具体问题
本项目以scrapy框架为基础,以爬取电影的名字,电影评分,电影图片url为起点,通过对网站的html页面源码进行分析,提出以下具体问题:如何对源码中的数据进行提取,使用哪个解析语法进行提取比较方便,如何防止反爬,如何实现将爬取的数据保存在本地和mysql中,数据库连接还需要建立何种连接。
1.2.2 项目研究的范围
爬虫中可以用到的爬取第三方库很多,比如urllib库、requests库、selenium库、scrapy库,这些都可以实现将想要的数据爬取下来,每个库都有各自的优点,我综合考虑以后,本次项目采用srapy库建立scrapy框架,研究各个框架的组成部分,分析框架的实现原理,进而实现数据的爬取以及数据的保存。以此为研究范围,重点是对数据如何爬取这一块进行研究。
1.3 项目研究的目的与意义
本项目的目的在于更深入的理解scrapy 框架的架构过程,理解五大基本构成,即调度器(Scheduler)、下载器(Downloader)、爬虫(Spider)和实体管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)的作用和用法,得到爬取的电影数据后,从大量爬取的电影数据中,对评分较高的电影选择进行观看。这就此次项目的目的。
而此次项目的意义在于熟练网络爬虫这门技术。互联网成了海量信息的载体:互联网目前是分析市场趋势、监视竞争对手或者获取销售线索的最佳场所,数据采集以及分析能力已成为驱动业务决策的关键技能。如何有效地提取并利用这些信息成了一个巨大的挑战,而网络爬虫是一种很好的自动采架数据的通用手段。
现如今是大数据的时代,信息爆炸了,互联网的数据呈现倍增的趋势,如何高效地获取互联网中感兴趣的内容并为所用是目前数据挖掘领域增值的一个重要方向。网络爬虫正是出于这个目的,迎来了新一波的振兴浪潮,成为近几年迅速发展的热门技术。许多企业需要数据来分析用户的行为,自己产品的不足以及竞争对手的信息等,而这一切的首要条件就是数据的采集。网络爬虫的价值就在于数据的价值,在互联网社会中,数据是无价之宝,一切皆是数据,谁拥有大量有用的数据,谁就拥有了决策的主动权。巧妇难为无米之炊,在一些应用领域中,如果没有网络爬虫为他们抓取数据,再好的算法和模型也得不到结果。而且没有数据进行机器学习建模,也形成不了能解决实际问题的模型。因此在目前炙手可热的人工智能领域,网络爬虫越来越起到数据生产者的关键作用,没有网络爬虫,数据挖掘、人工智能就成了无源之水和无本之木。这就是此次项目研究的根本意义。
二、项目总体设计及说明
2.1 项目研究的主要内容
项目主要是对scrapy框架的工作原理进行研究以及项目框架如何创建,理解各个组件的作用和用法。然后对网页的html页面进行研究,分析豆瓣网面是通过get请求还是post请求来请求页面,分析完成后,对第一张网页源码进行分析,定位到想要爬取的数据,利用xpath插件和xpath语法来检索想要爬取的数据,在scrapy框架的爬虫模块中定制自己的爬虫。接着研究的内容就是实现将爬取的电影数据以json文件的格式保存在本地文件夹中,得到的图片地址把图片下载到本地的文件夹中。最后就是研究实现在scrapy框架中与mysql数据库的连接,连接成功后将数据保存在创建的表中。
2.2 项目环境说明
(1)本次项目是以win10的操作系统和python3.8的版本为基本环境
(2)在chrome浏览器中对网页源代码进行分析
(3)在pycharm2021中进行代码编写
(4)需要scrapy库,urllib库等第三方库支持
(5)在mysql5.7中存储数据
2.3 项目研究的进度安排
(1)第一天:对scrapy框架进行学习研究。
(2)第二天:创建scrapy项目,对网页源码进行大量分析,构建想要爬取内容的xpath表达式。
(3)第三天:对框架里面的代码进行编写。
(4)第四天:编写数据库相应的表,建立与数据库之间的连接,对写好的代码进行测试,解决一些出现的bug。
(5)第五天:编写项目报告及程序调优。
2.4 总体设计方案
2.4.1 总体设计
第一步:scrapy项目创建
首先创建douban_01项目,再创建douban.py文件,明确各个模块中需要实现哪些类和哪些方法。
第二步:对网页的html页面源码进行分析
进入豆瓣Top250网页打开检查,定位到需要爬取数据的源码中,分析各个标签和子标签,构建xpath表达式,利用xpath插件对写好的xpath表达式进行验证。
第三步:设计实现爬虫核心功能的文件
进入到pycharm中,先编写定义数据结构的items.py,再编写douaban.py里面的代码,进入管道pipelines.py编写爬取的数据需要保存到文件夹和数据库的类,然后在settings.py中设置这几个类的优先级及一些其他需要编写的代码。
第四步:建立数据库表
在CMD终端中运行Mysql服务,建立一个叫douban的数据库,在这个数据库下建立一个film的表。
第五步:运行程序
在CMD终端中的spiders文件夹下输入运行命令,进行爬虫的运行。具体流程包括“引擎”索要URL, 调度器调度完后将第一个URL出队列返回给引擎,引擎经由下载器中间件将该URL交给下载器去下载 response对象,下载器得到响应对象后,将响应结果交给引擎,再经由中间件将响应结果交给douban.py,douabn.py对响应结果进行处理分析,提取出所需要的数据。
2.4.2 流程图
2.4.3 详细设计说明
(1)创建项目
创建douban_01项目:scrapy startproject douban_01。
创建douabn.py文件:scrapy genspider douban mivie.douban.com。
(2)items.py
首先在items,py中定义一个Douban01Item类,用来存取爬取的数据,根据需求定义了电影图片地址,电影名字和电影评分三个变量。
(3)douban.py
下面编写爬虫过程的主要代码:
1.首先定义爬虫的三个强制属性,即name、allowed_domains、start_urls,这三个属性在项目创建过程中就会生成。
name = "" :这个爬虫的识别名称,必须唯一。
allow_domains = [] 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只 爬取这个域名下的网页,本题中为链家域名“mivie.douban.com”。
start_urls = () :为要爬取的 URL 列表。爬虫从这里开始抓取数据。
2.接着编写对爬取的response进行信息获取操作,即编写parse函数。 先在初始网页中找到要爬取的数据的位置,并对应地复制它的 xpath,在xpath插件中验证爬取的内容,以同样的方法找到电影名字和电影评分所在的位置,并把这些信息保存在之前定义的Douban01Item类中。
3.这样只能得到第一个页面的数据,要想得到其他页面的数据,还需要进行分析,分析网页我发现在网址中第一页start后面是0,第二页start后面是25,之后八页都是此规律,那么只需要写好第一个网址,每次在start后面的数字加25,就可以得到剩余页面的网址,这里我使用if判断和递归来实现。
(4)pipelines.py
在pipelines.py 文件中定义了三个类,分别为Douban01Pipeline、DoubanDownloadPipeline、MysqlPipeline实现将爬取数据写到json文件中,将爬取到的图片地址通过方法下载图片到本地文件件中,将数据保存到Mysql数据库中。
在Douban01Pipeline类中定义了对数据的处理方法,此处定义 open_spider、 process_item 和 close_spider 三个函数。 此类是实现将爬取的数据保存为json文件中,故在 open_spider 函数中打开创建的flim.json文件,在 process_item 函数中,使用writer函数将每个item输出,全部输出完毕后关闭文件。
在DoubanDownloadPipeline类中利用urllib库中的urlreterieve将图片下载到本地的picture文件夹中。
在MysqlPipeline类中同样定义了 open_spider、 process_item 、 close_spider 和connect四个函数,open_spider函数实现与数据库的连接,它需要与connect函数匹配使用,在process_item函数实现将编写好的sql语句提交给数据库,close_spider函数实现关闭数据库连接。
(5)settings.py
在settings 中启用Douban01Pipeline、DoubanDownloadPipeline和MysqlPipeline,并设置优先级,在这个我设置的优先级为爬取数据转化为json文件的Douban01Pipeline类优先级最高,下载图片的Douban DownloadPipeline的类优先级次之,把数据保存到Mysql类的优先级设置为最低。
为了防止IP被封,在中间件中定义一个延迟函数类RandomDelayMiddleware,在settings中设置这个延迟时间为8s,并启动这个延时类。使得网站不会察觉这是一个爬虫在爬取。
在settings中设置了连接Mysql数据库用到的地址、端口号、用户名、密码、数据库的名字、表的名字和字符集编码。实现在管道中将数据库进行连接。
三、项目运行及测试结果
3.1 运行及测试过程
(1)项目的运行过程:
首先引擎向douban.py要豆瓣的网址,douban.py将豆瓣的网址提交给引擎,然后引擎将要爬取的豆瓣网址交给调度器,调度器会将豆瓣网址生成请求对象放入到指定的对列中,从对列中出队一个请求,引擎将请求交给下载器进行处理,下载器发送请求获取到豆瓣这个网址的的数据,这里因为有延时类,会隔几秒钟请求一次。下载器将得到的数据返回给引擎,引擎将数据再次给到douabn.py,douban.py通过xpath解析该数据,得到数据或者下一个豆瓣网址,douabn.py将数据或者网址交给引擎,引擎判断是网址还是数据,是数据交给管道处理,是网址交给调度器处理。以此一直进行,最后管道将数据通过三个类转成需要保留的形式。这就是程序运行的整个过程。详细的运行过程见下图。
(2)项目测试过程:
在我测试能否第一次进行数据爬取时,发现用于测试的print函数并没有打印内容,并且项目马上停止了,并没有在终端显示数据,我在仔细检查代码后,代码写的没有问题,然后我就继续找原因,在检查终端输出的数据可知,scrpay是默认遵循robots协议的,所以导致爬取不到数据。在settings将遵守robots协议那一项注释掉,问题就解决了。
测试用的print函数可以打印出来了,当我把douban.py中的代码写好后,运行后并没有数据,项目也是马上就停止了。
通过查找资料,发现造成这样的原因是直接向网页进行请求爬取,服务端一下就认出这是一个爬虫程序,所以需要设置请求头和cookie,这样可以伪装成一个浏览器进行爬取。在settings中设置这些参数后,发现爬取的数据有了。
爬取到数据以后,还需要将数据进行保存下来,于是我在管道中写了三个类来保存数据,在编写保存为json文件这个类时,我一开始只用了一个函数来实现,但是我发现这样是不好的,每次代码执行需要把文件打开,写入内容,再关闭。这样会使程序运行的效率降低,有什么办法能实现把文件打开,直到内容写完,再关闭文件。于是我就开始查找资料,在网上找到了类似的代码,于是我照着网上的代码把这个类完善了一下,测试后,内容也全部写进去了。
在编写管道中的第二个类时,测试发现picture这个文件夹并没有保存进去图片,终端显示找不到当前工程或者目录,我就在各个子文夹下移动picture文件夹进行测试,最后在spiders这个文件夹下测试成功,图片也保存进去了。
在编写管道中第三个类时,编写完成后,测试发现mysql数据库并没有保存进去数据,查询资料,得在mysql中设置一下允许远程访问,设置完成后,测试还是无法传入数据,经过检查,发现是主机名写错了,因为我连的是本地的数据库,需要把主机名改为localhost,修改完成后,运行程序,在mysql中查询数,数据现在显示出来。
3.2 运行结果及说明
(1)在终端中数据的显示:
(2)在json文件中显示爬取到的数据:
(3)在picture文件夹中保存的爬取数据:
(4)在Mysql数据库中保存的爬取数据:
四、项目总结体会及展望
通过这次实训,我收获了很多,一方面学习了很多python中的第三方库,比如Urllib库、randm库,还有最重要的一个scrapy框架。运用面向对象程序设计知识,利用python语言和scrapy框架设计和实现一个电影数据的爬取项目。在实现过程中,需利用面向对象程序设计理论的基础知识,充分体现出python语言关于类、继承和封装等核心概念。通过这几天的学习,更加丰富了我所学知识的宽度和广度,另一方面,我还提高了自己动手做项目,自己独立编程的能力。本次实训,是对我能力的进一步锻炼,也是一种考验。
实训是一个人综合能力的检验。要想优秀完成工作,除了基础知识功底深厚外,还需要有一定的实践动手能力、应对突发状况的能力。实训是学习一门新知识,大家都是从零基础开始,但是完成项目的效率有的高有的低,这大概就是实践能力高低者的区别所在吧。此次实训,让我学会了冷静思考,从容面对自己遇到的问题。一个星期的实训,不长不短,不易不难,但每一天的学习笔记都是自己用心记的,每一次的代码编写都是根据自己的想法思路来设计算法的,因为只有靠自己,才能真正把项目做好,同时我也学会了冷静思考,遇到错误上网搜集资料并解决问题,正是这样的状态,本次项目我能有效率的完成,而在完成之后,自己特别有成就感,自信心也大大增强了。作为学生,我要树立终身学习的观念,不断提高和充实自己。诚然,我们会被日常的琐事烦恼,但作为学生的我们,目前主要的任务仍是学习。在信息时代,知识更迭飞快,每天都有新问题和新技术,紧跟时代的步伐。在以后的学习生活中,我会把自己更多的时间投入学习,通过学习,一方面弥补自己知识的贫瘠,另一方面让自己的精神生活、精神世界更加丰富。作为学生,学习能力只是证明自己的一个维度,只有多维自由全方面的发展才是新时代合格的学生。以前的我也是那个只注重学业绩点的学生,后来在经历了一些事情后,一个人的情商、一个人的智慧、一个人的格局对其来说也是很重要的。一个人学生纵使学业成绩异常优秀,但其做人让人心生厌恶,也算是不是一个合格的学生,至少我不会把其视为合格的学生。
总的来说,这次实训不仅使我学到了知识,丰富了经验。也帮助我缩小了实践和理论的差距,使我对爬虫有了进一步了解。这次实训将会有利于我更好的适应以后毕业的工作。我会把握和珍惜实训的机会,在未来的工作中我会把学到的理论知识和实践经验不断的应用到实际工作中,为实现理想而努力。最后,我要感谢学院组织的这次十分有意义的实训,使我们学到了很多,也领悟了很多。同时,也要感谢为这次实训默默付出的老师们,是他们辛苦的汗水使这次实训得以完美结束。最后,这次实训让我深刻体会到学习是一件持之以恒的事,编程设计需要耐心、细心以及恒心,一步步的编写修改,调试运行都使我离最终目标程序更进一步,不能遇到难题就退缩,不能半途而废。
五、项目主要源程序
class Douban01Item(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
src = scrapy.Field()
name =scrapy.Field()
evaluable = scrapy.Field()
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/top250?start=0&filter=']
base_url = "https://movie.douban.com/top250?start="
start = 25;
def parse(self, response):
list_src = response.xpath('//*[@id="content"]/div/div/ol/li')
for i in list_src:
src = i.xpath('./div/div/a/img/@src').extract_first()
name = i.xpath('./div/div/a/img/@alt').extract_first()
evaluable = i.xpath('./div/div//span[@class="rating_num"]/text()').extract_first()
flim = Douban01Item(src =src,name =name,evaluable = evaluable)
# 获得一个flim交给pipelines
yield flim
if self.start<=(25*10):
self.start =self.start+25
url = self.base_url + str(self.start) + '&filter='
yield scrapy.Request(url = url,callback=self.parse)
class Douban01Pipeline:
def open_spider(self,spider):
self.fp = open("flim.json","w",encoding="utf-8")
def process_item(self, item, spider):
self.fp.write(str(item))
return item
def close_spider(self,spider):
self.fp.close()
import urllib.request
class DoubanDownloadPipeline:
def process_item(self, item, spider):
url = item.get("src")
filename = './picture/' + item.get("name") + '.jpg'
urllib.request.urlretrieve(url=url,filename=filename)
return item
from scrapy.utils.project import get_project_settings
import pymysql
class MysqlPipeline:
def open_spider(self,spider):
settings = get_project_settings()
self.host = settings['DB_HOST']
self.port =settings['DB_PORT']
self.user =settings['DB_USER']
self.password =settings['DB_PASSWORD']
self.name =settings['DB_NAME']
self.charsert =settings['DB_CHARSET']
self.connect()
def connect(self):
self.conn =pymysql.connect(
host = self.host,
port = self.port,
user = self.user,
password = self.password,
db = self.name,
charset = self.charsert
)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
sql = 'insert into film(name,evaluable,src) values("{}","{}","{}")'.format(item["name"],item["evaluable"],item["src"])
# 执行sql语句
self.cursor.execute(sql)
# 提交sql语句
self.conn.commit()
return item
def close_spider(self,spider):
self.conn.close()
self.cursor.close()
ITEM_PIPELINES = {
'douban_01.pipelines.Douban01Pipeline': 300,
'douban_01.pipelines.DoubanDownloadPipeline':301,
'douban_01.pipelines.MysqlPipeline':302,
}
import random
import time
class RandomDelayMiddleware(object):
def __init__(self, delay):
self.delay = delay
@classmethod
def from_crawler(cls, crawler):
delay = crawler.spider.settings.get("RANDOM_DELAY", 10)
if not isinstance(delay, int):
raise ValueError("RANDOM_DELAY need a int")
return cls(delay)
def process_request(self, request, spider):
delay = random.randint(0, self.delay)
time.sleep(delay)