爬取豆瓣电影8608部电影的信息-模拟Ajax请求的应用

一入爬虫深似海,回头还是在入门。

豆瓣电影提供最新的电影介绍及评论包括上映影片的影讯查询及购票服务。你可以记录想看、在看和看过的电影电视剧,顺便打分、写影评。极大地方便了人们的生活。1

豆瓣电影是国内较大的电影讨论社区,代表了电影圈的风向和潮流(我没有收取任何广告费),豆瓣上文艺青年比较多,但是讨论和评价的人数数量很大,还是有一定可信度和代表性的,所以选择爬取豆瓣电影,主要还是因为它的网页结构比较简单。

本次爬取选择的网址是:https://movie.douban.com/tag/#/?sort=U&range=0,10&tags=电影 。网页打开后,可以看到最下面并不是分页显示的,而是一个“加载更多”,点击后整个页面并没有刷新,网址也没有改变,只是显示了更多的内容,由此可见这个页面使用的是异步加载。

爬取豆瓣电影8608部电影的信息-模拟Ajax请求的应用

首先我们通过Chrome浏览器的开发者模式,看一下这个Ajax请求是怎么样的。
爬取豆瓣电影8608部电影的信息-模拟Ajax请求的应用
可以看到Request URL是 https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=20 。不停的点加载更多,变化的只有start=20这个地方,这里的20指的是每一页显示的电影个数,start=0表示第一页,start=20表示第二页,start=40表示第三页,以此类推。经手动测试,start=9960是最后一页。
爬取豆瓣电影8608部电影的信息-模拟Ajax请求的应用
Ajax请求返回的是一个json格式的数据,Chrome浏览器的开发者模式非常人性化的有一个Preview的功能可以直观的看到解析后的内容。这里可以看到data包含了20个电影的列表,其中我们要提取的是主演(casts)、导演(directors)、评分(rate)、电影名字(title)。但是这些信息似乎不够,还缺少一些我们想要提取的比如类型、制片国家、上映时间等,这里可以看到json数据还有一个url地址,https://movie.douban.com/subject/3011091/ ,没错,这个就是这部电影详细信息的地址。
爬取豆瓣电影8608部电影的信息-模拟Ajax请求的应用
这个详细信息页面里面就包含了我们想要提取的信息。当然这个详细信息页面就不是Ajax请求返回的了,是一个实实在在的网址,我们可以通过解析原始的HTML文档获得想要的东西,这部分信息都包含在一个叫info的节点内。
爬取豆瓣电影8608部电影的信息-模拟Ajax请求的应用
目标确定好了,现在还有一个问题就是如何防止被封ip。通过网上搜索得到的非官方消息是豆瓣白天的访问限制频率是40次/分钟,晚上是60次/分钟,解决封ip的有效方法主流的有两种,一是使用代理,二是设置爬取的时间间隔。使用代理是很好的方法,但是也有很多缺点,比如稳定的代理是要收费的,而且在网上还搜到使用代理依然被封原始ip的案例。互联网的思想是信息共享,但是我们在享受免费资源的同时,也要考虑到网站服务器的压力,要做一个有素质的人,所以这里我们果断选择第二种方法(其实是还不太会弄代理)。

import requests
import time
import random
from openpyxl import Workbook
from bs4 import BeautifulSoup

lst=[] #存放爬取结果
error_list=[] #存放未能提取的页码
base_url='https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=%E7%94%B5%E5%BD%B1&start='
headers={
        'Host':'movie.douban.com',
        'Referer':'https://movie.douban.com/tag/',
        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
        }

StartTime=time.time()
for num in range(0,9961,20):
    num=(num-1)*20
    url=base_url+str(num)
    print('现在正在提取第%d页……' % (num//20+1))
    try:
        response=requests.get(url,headers=headers)
        if response.status_code==200:
            json=response.json()
        items=json.get('data')
        for item in items:
            response=requests.get(item.get('url')) #遍历每个url
            html=response.text
            soup=BeautifulSoup(html,'lxml')
            info=soup.find(id="info").get_text() #提取info节点内的文本
            summary=soup.find(property="v:summary").get_text() #提取剧情简介
            detail={} #以字典的形式存储每个电影提取的信息
            detail['名称']=item.get('title')
            detail['评分']=item.get('rate')
            detail['主演']='/'.join(item.get('casts'))
            detail['导演']='' #设置空值,以保证每部电影对应的字典都包含所有Key
            detail['类型']=''
            detail['制片国家/地区']=''
            detail['语言']=''
            detail['上映日期']=''
            detail['片长']=''
            for x in info.split('\n'):
                if x[:x.find(':')]=='导演':
                    detail['导演']=x[x.find(':')+1:].replace(' ','')
                elif x[:x.find(':')]=='类型':
                    detail['类型']=x[x.find(':')+1:].replace(' ','')
                elif x[:x.find(':')]=='制片国家/地区':
                    detail['制片国家/地区']=x[x.find(':')+1:].replace(' ','')
                elif x[:x.find(':')]=='语言':
                    detail['语言']=x[x.find(':')+1:].replace(' ','')
                elif x[:x.find(':')]=='上映日期':
                    detail['上映日期']=x[x.find(':')+1:].replace(' ','')
                elif x[:x.find(':')]=='片长':
                    detail['片长']=x[x.find(':')+1:].replace(' ','')
            detail['剧情简介']=summary.strip()
            lst.append(detail)
            time.sleep(random.uniform(0.1,2.0)) #设置一个随机等待时间,模拟人的操作,以防被封ip
    except:
        error_list.append(num//20+1) #保存未能提取的页码
EndTime=time.time()

print('正在存储数据……')
wb=Workbook() #存储到excel
ws=wb.active
col=1
for key in lst[0].keys():
    ws.cell(row=1,column=col,value=key)
    col+=1
for i in range(len(lst)):
    col=1
    for key in lst[0].keys():
        ws.cell(row=i+2,column=col,value=lst[i][key])
        col+=1		
save_name=r'C:\Users\Administrator\Desktop\MovieData.xlsx'
wb.save(save_name)
print('未能提取的页码:',error_list)
print('运行时间:%s' % (EndTime-StartTime))

整个爬取过程历时长达5个小时!!!!足以体现本人在追求真理的道路上,坚韧不拔,持之以恒的精神!
有少量页码没有成功,应该是网络原因,最终爬取的电影数目是8608部。
爬取的结果如下图,这个结果准备用于后续的分析。
爬取豆瓣电影8608部电影的信息-模拟Ajax请求的应用


  1. https://baike.baidu.com/item/豆瓣电影#reference-[1]-2989101-wrap ↩︎