很多时候,我们爬取一个网页,往往都带有下一页,我们不止要爬取当前页数据,也应该爬取下一页数据。
警告:此网站只是用于爬虫练手,请不要有任何不好的想法。
网站分析
进行爬虫之前首先需要了解网站结构,通过查看网站大致分析如下,上面是电影的一些信息,排名、封面,电影名等。除了最下面的列表的分页信息。77dianshi一页的电影信息不止这么点。
创建爬虫项目
- 创建scrapy项目,项目名称叫 scrapy_demo
$ scrapy startproject scrapy_demo
- 进入 scrapy_demo 项目中
$ cd scrapy_demo
- 生成一个爬虫
爬虫名称:movie
爬取范围:77dianshi.com
$ scrapy genspider movie 77dianshi.com
Created spider 'movie' using template 'basic' in module:
scrapy_demo.spiders.movie
- 进入movie.py 文件
import scrapy
class MovieSpider(scrapy.Spider):
name = 'movie'
allowed_domains = ['77dianshi.com']
start_urls = ['http://77dianshi.com/']
def parse(self, response):
pass
- 修改 start_urls (如下)
import scrapy
class MovieSpider(scrapy.Spider):
name = 'movie' # 项目名称
allowed_domains = ['77dianshi.com'] # 爬取范围
start_urls = ['http://www.77dianshi.com/kdongzuopian/'] # 爬取地址
def parse(self, response):
pass
网页结构分析
- 使用chrome xpath插件。
经过调试,所有的电影信息都用ul包围,并就放在li中。
网页结构
- 编写脚本
编写parse 代码
def parse(self, response):
li_list=response.xpath('//ul[@class="fed-list-info fed-part-rows"]/li')
for li in li_list:
print(li)
调试;保证我们的爬取没有问题
$ scrapy crawl movie
2021-06-11 21:33:01 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
<Selector xpath='//ul/li' data='<li class="fed-pull-left"><a class="f...'>
<Selector xpath='//ul/li' data='<li class="fed-pull-left"><a class="f...'>
<Selector xpath='//ul/li' data='<li class="fed-pull-left"><a class="f...'>
<Selector xpath='//ul/li' data='<li class="fed-pull-left"><a class="f...'>
<Selector xpath='//ul/li' data='<li class="fed-pull-left"><a class="f...'>
<Selector xpath='//ul/li' data='<li class="fed-pull-left"><a class="f...'>
<Selector xpath='//ul/li' data='<li class="fed-pull-left"><a class="f...'>
<Selector xpath='//ul/li' data='<li class="fed-col-sm2"><a class="fed...'>
<Selector xpath='//ul/li' data='<li class="fed-col-sm2 fed-this"><a c...'>
<Selector xpath='//ul/li' data='<li class="fed-col-sm2"><a class="fed...'>
<Selector xpath='//ul/li' data='<li class="fed-col-sm2"><a class="fed...'>
...
输入 scrapy crawl movie 后能获取以上信息,证明我们能正常获取数据就没问题了。
数据分析
可上面这样的结果并不会我们想要的数据,所以我们需要更加详细的分析每个li中的结果是什么。
单个li的数据结构如下:
<li class="fed-list-item fed-padding fed-col-xs4 fed-col-sm3 fed-col-md2 xh-highlight"><a class="fed-list-pics fed-lazy fed-part-2by3" href="/t/wumianjuexing/" data-original="https://img.huishij.com/upload/vod/20210610-1/ba6cb9b6c0161b3e46406a154fdec464.jpg" style="display: block; background-image: url("https://img.huishij.com/upload/vod/20210610-1/ba6cb9b6c0161b3e46406a154fdec464.jpg");"><span class="fed-list-play fed-hide-xs"></span><span class="fed-list-score fed-font-xii fed-back-green">6.0</span><span class="fed-list-remarks fed-font-xii fed-text-white fed-text-center">HD</span></a><a class="fed-list-title fed-font-xiv fed-text-center fed-text-sm-left fed-visible fed-part-eone" href="/t/wumianjuexing/">无眠觉醒</a><span class="fed-list-desc fed-font-xii fed-visible fed-part-eone fed-text-muted fed-hide-xs fed-show-sm-block">吉娜·罗德里格兹,沙米尔·安德森,詹妮弗·杰森·李,阿丽亚娜·格林布拉特,巴里·佩珀,弗兰西丝·费舍,吉尔·贝罗斯,菲恩·琼斯,塞巴斯蒂安·皮戈特,塞尔吉奥·齐奥,亚历克斯·豪斯,卢修斯·霍约斯,特洛文·海斯,肖恩·艾哈迈德,朱莉娅·迪扬,罗伯特·巴佐齐,柴·瓦拉达雷斯,凯特琳娜·塔克西亚,玛莎·格尔文,埃利亚斯·艾德拉基,Michael,Hough</span></li>
首先有两个a标签的,
第一个a标签
<a class="fed-list-pics fed-lazy fed-part-2by3" href="/t/wumianjuexing/" data-original="https://img.huishij.com/upload/vod/20210610-1/ba6cb9b6c0161b3e46406a154fdec464.jpg" style="display: block; background-image: url("https://img.huishij.com/upload/vod/20210610-1/ba6cb9b6c0161b3e46406a154fdec464.jpg");"><span class="fed-list-play fed-hide-xs"></span><span class="fed-list-score fed-font-xii fed-back-green">6.0</span><span class="fed-list-remarks fed-font-xii fed-text-white fed-text-center">HD</span></a>
data-original:存储了电影封面
三个span标签:第一个并没有数据,第二span标签存有评分,第三个span标签存有 HD 的字样(不清楚是什么意思,知道的朋友告诉我一下)。
def parse(self, response):
li_list=response.xpath('//ul[@class="fed-list-info fed-part-rows"]/li')
for li in li_list:
item={} # 用于封装数据
# 获取第一个 a 标签
a1=li.xpath("./a[1]")
#//封面
item["cover"]=li.xpath("./a[1]/@data-original").extract_first()
# 评分
item["score"]=a1.xpath("./span[2]/text()").extract_first()
# HD
item["hd"]=a1.xpath("./span[3]/text()").extract_first()
print(item)
第一个a标签结果如下:
$ scrapy crawl movie --nolog
{'cover': 'https://img.huishij.com/upload/vod/20210610-1/ba6cb9b6c0161b3e46406a154fdec464.jpg', 'score': '6.0', 'hd': 'HD'}
{'cover': 'http://ae01.alicdn.com/kf/Ua7d5187008104f1b8da8d322d3e160ffv.jpg', 'score': '5.0', 'hd': '共12集,完结'}
{'cover': 'https://img.huishij.com/upload/vod/20210609-1/dba912b28f71400c85e98a75ec193aaa.jpg', 'score': '6.0', 'hd': 'HD'}
{'cover': 'http://ae01.alicdn.com/kf/U3dece43d297848f2b5dd58ff0e070eb2D.jpg', 'score': '9.0', 'hd': 'HD'}
{'cover': 'https://img.huishij.com/upload/vod/20210608-1/47c74371cbdf5bb97f02d7c0f06d7cd2.jpg', 'score': '3.0', 'hd': 'HD'}
{'cover': 'https://img.huishij.com/upload/vod/20210607-1/df394b70390b62a5358aeed3163f3131.jpg', 'score': '10.0', 'hd': 'HD'}
{'cover': 'https://img.huishij.com/upload/vod/20210606-1/86dcf8922c009db93ae28458bc51125f.jpg', 'score': '4.0', 'hd': 'HD'}
...
第二个a标签存放了封面名称,最后一个span标签存放了演员名单信息,并不会很多,这里就一块处理了。
<a class="fed-list-title fed-font-xiv fed-text-center fed-text-sm-left fed-visible fed-part-eone" href="/t/wumianjuexing/">无眠觉醒</a><span class="fed-list-desc fed-font-xii fed-visible fed-part-eone fed-text-muted fed-hide-xs fed-show-sm-block">吉娜·罗德里格兹,沙米尔·安德森,詹妮弗·杰森·李,阿丽亚娜·格林布拉特,巴里·佩珀,弗兰西丝·费舍,吉尔·贝罗斯,菲恩·琼斯,塞巴斯蒂安·皮戈特,塞尔吉奥·齐奥,亚历克斯·豪斯,卢修斯·霍约斯,特洛文·海斯,肖恩·艾哈迈德,朱莉娅·迪扬,罗伯特·巴佐齐,柴·瓦拉达雷斯,凯特琳娜·塔克西亚,玛莎·格尔文,埃利亚斯·艾德拉基,Michael,Hough</span>
编写程序
def parse(self, response):
li_list=response.xpath('//ul[@class="fed-list-info fed-part-rows"]/li')
for li in li_list:
item={} # 用于封装数据
# 获取第一个 a 标签
a1=li.xpath("./a[1]")
# 封面
item["cover"]=li.xpath("./a[1]/@data-original").extract_first()
# 评分
item["score"]=a1.xpath("./span[2]/text()").extract_first()
# HD
item["hd"]=a1.xpath("./span[3]/text()").extract_first()
# 电影名称
item["movie_name"]=li.xpath("./a[2]/text()").extract_first()
# 演员名单
item["cast_list"]=li.xpath("./span/text()").extract_first()
print(item)
最终数据结果
$ scrapy crawl movie --nolog
{'cover': 'https://img.huishij.com/upload/vod/20210610-1/ba6cb9b6c0161b3e46406a154fdec464.jpg', 'score': '6.0', 'hd': 'HD', 'movie_name': '无眠觉醒', 'cast_list': '吉娜·罗德里格兹,沙米尔·安德森,詹妮弗·杰森·李,阿丽亚娜·格林布
拉特,巴里·佩珀,弗兰西丝·费舍,吉尔·贝罗斯,菲恩·琼斯,塞巴斯蒂安·皮戈特,塞尔吉奥·齐奥,亚历克斯·豪斯,卢修斯·霍约斯,特洛文·海斯,肖恩·艾哈迈德,朱莉娅·迪扬,罗伯特·巴佐齐,柴·瓦拉达雷斯,凯特琳娜·塔克西亚,玛莎·格尔文,埃利亚斯·艾德拉基,Michael,Hough'}
{'cover': 'http://ae01.alicdn.com/kf/Ua7d5187008104f1b8da8d322d3e160ffv.jpg', 'score': '5.0', 'hd': '共12集,完结', 'movie_name': '无间道', 'cast_list': '刘德华,梁朝伟,黄秋生,曾志伟,郑秀文,陈慧琳,陈冠希,余文乐,杜汶泽,林家栋,萧
亚轩'}
{'cover': 'https://img.huishij.com/upload/vod/20210609-1/dba912b28f71400c85e98a75ec193aaa.jpg', 'score': '6.0', 'hd': 'HD', 'movie_name': '贼世至尊1999', 'cast_list': 'Alec,Baldwin,Andre,Braugher,Michael,Jai,White'}
{'cover': 'http://ae01.alicdn.com/kf/U3dece43d297848f2b5dd58ff0e070eb2D.jpg', 'score': '9.0', 'hd': 'HD', 'movie_name': '少林小子', 'cast_list': '李连杰,黄秋燕,潘清福,于承惠,于海,胡坚强'}
{'cover': 'https://img.huishij.com/upload/vod/20210608-1/47c74371cbdf5bb97f02d7c0f06d7cd2.jpg', 'score': '3.0', 'hd': 'HD', 'movie_name': '填字遊戲事件簿:致命謎題', 'cast_list': 'Lacey,Chabert,John,Kapelos,Brennan,Elliott'}
{'cover': 'https://img.huishij.com/upload/vod/20210607-1/df394b70390b62a5358aeed3163f3131.jpg', 'score': '10.0', 'hd': 'HD', 'movie_name': '半狼传说', 'cast_list': '子剑,吴宣萱,任天野'}
{'cover': 'https://img.huishij.com/upload/vod/20210606-1/86dcf8922c009db93ae28458bc51125f.jpg', 'score': '4.0', 'hd': 'HD', 'movie_name': '国家安全2010', 'cast_list': '内详'}
{'cover': 'https://img.huishij.com/upload/vod/20210605-1/e9e7b4fff221c92464b3786fa3340dee.jpg', 'score': '7.0', 'hd': 'HD', 'movie_name': '卡拉鹰', 'cast_list': 'Ricardo,Darín,Martina,Gusman'}
{'cover': 'https://img.huishij.com/upload/vod/20210605-1/558ca1cc9423a9c08ff8e65c42acfef2.jpg', 'score': '6.0', 'hd': 'HD', 'movie_name': '拳神2010', 'cast_list': 'Mahesh,Babu,Anushka,Shetty'}
{'cover': 'https://img.huishij.com/upload/vod/20210605-1/9a82882e959add4063b6bd7b7ebfb0fb.jpg', 'score': '8.0', 'hd': 'HD', 'movie_name': '三日危情', 'cast_list': '罗素·克劳,伊丽莎白·班克斯,泰·辛普金斯,奥利维亚·王尔德,连姆·尼
森,乔纳森·塔克,布莱恩·丹内利,瑞秋·迪肯,连尼·詹姆斯,杰森·贝盖,詹姆斯·兰索恩,莫兰·阿提艾斯,艾莎·辛德斯,丹尼尔·斯特恩'}
{'cover': 'https://img.huishij.com/upload/vod/20200627-1/b90ab5f7cf189c8556d9947971c2581b.jpg', 'score': '5.0', 'hd': 'HD', 'movie_name': '翻转', 'cast_list': '帕克·波西,迈克尔·拉帕波特,布鲁斯·邓恩,迈克尔·库立兹,保罗·莱维斯克
'}
...
这样我们就爬取了当前整页的电影信息
movie.py的完整代码如下:
import scrapy
class MovieSpider(scrapy.Spider):
name = 'movie' # 项目名称
allowed_domains = ['77dianshi.com'] # 爬取范围
start_urls = ['http://www.77dianshi.com/kdongzuopian/'] # 爬取地址
def parse(self, response):
li_list=response.xpath('//ul[@class="fed-list-info fed-part-rows"]/li')
for li in li_list:
item={} # 用于封装数据
# 获取第一个 a 标签
a1=li.xpath("./a[1]")
# 封面
item["cover"]=li.xpath("./a[1]/@data-original").extract_first()
# 评分
item["score"]=a1.xpath("./span[2]/text()").extract_first()
# HD
item["hd"]=a1.xpath("./span[3]/text()").extract_first()
# 电影名称
item["movie_name"]=li.xpath("./a[2]/text()").extract_first()
# 演员名单
item["cast_list"]=li.xpath("./span/text()").extract_first()
print(item)
获取下页的地址
爬取完第一页的数据之后,如何爬取下一页的数据呢?我们只需要获取下一页的url地址即可。
使用xpath工具分析,获取包含下页文本内容a标签的href信息
获取下一页href
点击下页:url是这样的:http://www.77dianshi.com/kdongzuopian-2/ ,也就是说将获取下页的href地址与http://www.77dianshi.com进行拼接即可。
编写程序,获取下页地址
def parse(self, response):
li_list=response.xpath('//ul[@class="fed-list-info fed-part-rows"]/li')
for li in li_list:
item={} # 用于封装数据
# 获取第一个 a 标签
a1=li.xpath("./a[1]")
# 封面
item["cover"]=li.xpath("./a[1]/@data-original").extract_first()
# 评分
item["score"]=a1.xpath("./span[2]/text()").extract_first()
# HD
item["hd"]=a1.xpath("./span[3]/text()").extract_first()
# 电影名称
item["movie_name"]=li.xpath("./a[2]/text()").extract_first()
# 演员名单
item["cast_list"]=li.xpath("./span/text()").extract_first()
# 获取下页的url
next_href=response.xpath('//div[@class="fed-page-info fed-text-center"]/a[contains(text(),"下页")]/@href').extract_first()
next_url="http://www.77dianshi.com"+next_href
print(next_url)
结果如下:
$ scrapy crawl movie --nolog
http://www.77dianshi.com/kdongzuopian-2/
获取到下页的url之后,还需要思考一个问题,若没有下一页了肯定会报错,这不是我们想看到的。所以下页的href不会都可能存在,所以得做好判断,防止报错。
我们看看最后一页是的什么样子最后一页从上面看出,下页的href都会存在,只不过最后一页的href就是当前页的href。
判断是否为最后一页
def parse(self, response):
li_list=response.xpath('//ul[@class="fed-list-info fed-part-rows"]/li')
for li in li_list:
item={} # 用于封装数据
# 获取第一个 a 标签
a1=li.xpath("./a[1]")
# 封面
item["cover"]=li.xpath("./a[1]/@data-original").extract_first()
# 评分
item["score"]=a1.xpath("./span[2]/text()").extract_first()
# HD
item["hd"]=a1.xpath("./span[3]/text()").extract_first()
# 电影名称
item["movie_name"]=li.xpath("./a[2]/text()").extract_first()
# 演员名单
item["cast_list"]=li.xpath("./span/text()").extract_first()
# 获取下页的url
next_href=response.xpath('//div[@class="fed-page-info fed-text-center"]/a[contains(text(),"下页")]/@href').extract_first()
next_url="http://www.77dianshi.com"+next_href
#获取当前页的url
current_url=response.url
# 通过判断是否以next_href为结尾,来判断是否是最后一页
if not current_url.endswith(next_href):
print("最后一页")
else:
print("不是最后一页")
爬取下页的电影信息
通过上面的准备工作,完成了当前页的数据爬取,下页url地址获取及最后一页的判断。接下来我们将使用scrapy来爬取下一页的数据。
止住:先别着急,为了保证爬取数据更稳定,我们需要修改一些参数。
settings.py配置:
DOWNLOAD_DELAY = 3 # 设置爬取延长时间,这里设置为三秒。爬取过快,可能会导致IP被封
ROBOTSTXT_OBEY = False # 我称这个为撕毁**君子协议**
# 自定义USER_AGENT
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36'
完整的代码
import scrapy
class MovieSpider(scrapy.Spider):
name = 'movie' # 项目名称
allowed_domains = ['77dianshi.com'] # 爬取范围
start_urls = ['http://www.77dianshi.com/kdongzuopian/'] # 爬取地址
def parse(self, response):
li_list=response.xpath('//ul[@class="fed-list-info fed-part-rows"]/li')
for li in li_list:
item={} # 用于封装数据
# 获取第一个 a 标签
a1=li.xpath("./a[1]")
# 封面
item["cover"]=li.xpath("./a[1]/@data-original").extract_first()
# 评分
item["score"]=a1.xpath("./span[2]/text()").extract_first()
# HD
item["hd"]=a1.xpath("./span[3]/text()").extract_first()
# 电影名称
item["movie_name"]=li.xpath("./a[2]/text()").extract_first()
# 演员名单
item["cast_list"]=li.xpath("./span/text()").extract_first()
print(item)
# 获取下页的url
next_href=response.xpath('//div[@class="fed-page-info fed-text-center"]/a[contains(text(),"下页")]/@href').extract_first()
next_url="http://www.77dianshi.com"+next_href
#获取当前页的url
current_url=response.url
#为了方便查看进度,打印当前url地址
print("已经爬取了到了:",current_url,"下页地址:",next_url)
# 通过判断是否以next_href为结尾,来判断是否是最后一页
if not current_url.endswith(next_href):
# url:下一页的url地址
# callback:需要交由那个parse方法处理(可以自定义),因为下一页的数据结构,和当前页的数据一样,所以处理方式都是一样的。若不一样,那么需要自定义
yield scrapy.Request(url=next_url,callback=self.parse)
else:
print("爬取完毕")
爬取结果
...
{'cover': 'http://ae01.alicdn.com/kf/U5f88cacadfe24a4894ed96f43676d1b1i.jpg', 'score': '4.0', 'hd': 'BD高清', 'movie_name': '血姬传', 'cast_list': '任娇,王铭,陈超良,张星阑'}
{'cover': 'https://cdn1.mh-pic.com/upload/vod/2019-10-05/157026432110.jpg', 'score': '7.0', 'hd': 'HD', 'movie_name': '钟离凰', 'cast_list': '岳辛,陈希郡,赵子络,何索,于歆童,李佳蔚'}
{'cover': 'https://cdn1.mh-pic.com/upload/vod/2019-10-05/15702721910.jpg', 'score': '7.0', 'hd': 'BD高清', 'movie_name': '半条命3:特种兵之战', 'cast_list': '楚镇,郭云飞,靳美玲'}
已经爬取了到了: http://www.77dianshi.com/kdongzuopian-17/ 下页地址: http://www.77dianshi.com/kdongzuopian-18/
{'cover': 'http://ae01.alicdn.com/kf/Uf774144860144247bbea9d4861c1e7a04.jpg', 'score': '5.0', 'hd': 'HD', 'movie_name': '看见我和你', 'cast_list': '杜海涛,刘璇,吴昕,唐禹哲,王雯雯,汤镇业,李健仁,陈志雄,孙丹丹,郑伊涵'}
{'cover': 'http://ae01.alicdn.com/kf/U7402fe10e7564417b3443a322e3f7dd3O.jpg', 'score': '8.0', 'hd': 'HD', 'movie_name': '分歧者2:绝地反击', 'cast_list': '谢琳·伍德蕾,提奥·詹姆斯,凯特·温丝莱特,奥克塔维亚·斯宾瑟'}
{'cover': 'http://ae01.alicdn.com/kf/U6a124ba5fdcd4cf5b99f133e16f012000.jpg', 'score': '7.0', 'hd': 'HD', 'movie_name': '分歧者:异类觉醒', 'cast_list': '谢琳·伍德蕾,提奥·詹姆斯,艾什莉·贾德,杰·科特尼'}
{'cover': 'http://ae01.alicdn.com/kf/U0c95ac9b8d964a24b8da02686bd971dbI.jpg', 'score': '7.0', 'hd': 'HD', 'movie_name': '杀人者唐斩', 'cast_list': '张丰毅,关之琳,莫少聪,张光北'}
....
目前爬取到了前18页数据:
已经爬取了到了: http://www.77dianshi.com/kdongzuopian-17/ 下页地址: http://www.77dianshi.com/kdongzuopian-18/
总结:
scrapy.Request 能构建一个requests,同时指定提取数据的callback函数。
scrapy.Request知识点:
scrapy.Request(url,callbock,method='GET',headers,body,cookies,meta,dont_filter=False)
- url:请求地址
- callbock:执行函数
- method:请求方式POST/GET
- headers:请求头
- body:请求体
- cookies:cookies,有专门的地方存放,通常这里不用指定
- meta:元数据信息
- dont_filter:是否去重当前的url
scrapy.Request常用参数:
- url :请求下一次的url地址。
- callbock:指定传入的url交给哪个解析函数去处理。
- meta:实现在不同的解析函数中传递数据,meta默认会携带部分信息,比如下载延迟,请求深度等。
- dont_filter:让scrapy的去重不会过滤当前url,scrapy默认有url去重的功能,对需要重复请求的url有重要用途。
最后一点说明:
对于以后的数据存档操作(保存到本地磁盘或存储到数据库中)我们都应该在 movie.py 这个文件中操作。我们应该把数据交给pipelines.py来做。
需要怎么做呢?需要使用 yield 关键字。
具体操作如下:
- 需要将 print(item) 改成 yield item。
for li in li_list:
item={} # 用于封装数据
# 获取第一个 a 标签
a1=li.xpath("./a[1]")
# 封面
item["cover"]=li.xpath("./a[1]/@data-original").extract_first()
# 评分
item["score"]=a1.xpath("./span[2]/text()").extract_first()
# HD
item["hd"]=a1.xpath("./span[3]/text()").extract_first()
# 电影名称
item["movie_name"]=li.xpath("./a[2]/text()").extract_first()
# 演员名单
item["cast_list"]=li.xpath("./span/text()").extract_first()
#print(item)
yield item
- 修改 settings.py 文件
将 ITEM_PIPELINES 注释打开
。 - 进入 pipelines.py 文件中
获取item数据,模拟入库操作
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
class ScrapyDemoPipeline:
def process_item(self, item, spider):
#获取item 数据
print("模拟将数据入库:",item) #这里的打印和movie.py 里打印是一样的。