我们知道js可以操纵DOM,可以请求后台,因此我们最终看到的html页面可能是js执行的结果,如果我们单纯用爬虫获取动态页面的html,看到的可能就是一堆js
我自己总结了两种方式,1、获取后台接口,2、通过selenium+chromdriver,这篇博客先介绍第一种,这两种方式各有优劣
方式一:
动态页面有一个特点,它所需要的数据需要自己去请求后台,不是写死在html中的,因此只要找到这个后台接口即可,采用这种方式,优点是快,缺点是解析难度比较大,需要解析js或是抓包分析获得后台接口
方式二:
基本上可以为所欲为,因为本身就是模拟浏览器的行为,它可以自动解析js,生成最终的html页面,因为需要解析js,因此爬取速度较慢,解析js会占用较多的cpu计算资源,由于比较吃硬件,所以并发能力比较弱
下面以爬取豆瓣top 250的电影为例子,讲解第一种方式,之所以选择豆瓣,是因为豆瓣top250的后台接口几乎就暴露在我们面前,由于是练习,所以步骤比较繁琐:
点击加载更多,一定会发送数据给后台,只需要捕获请求即可,不需要去看冗长的js代码(并且js代码可能进行了混淆,导致可读性极差),使用chrome开发者工具等抓包工具抓取请求路径如下:
https://movie.douban.com/j/search_subjects?type=movie&tag=豆瓣高分&sort=rank&page_limit=20&page_start=20
该请求返回json数据,观察请求参数,page_limit应该和返回的json条目有关,返回的json格式如下:
{
"subjects": [
{
"rate": "9.0",
"cover_x": 3375,
"title": "少年派的奇幻漂流",
"url": "https:\/\/movie.douban.com\/subject\/1929463\/",
"playable": true,
"cover": "https://img3.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p1784592701.webp",
"id": "1929463",
"cover_y": 5000,
"is_new": false
}
]
}
这里单纯解析json格式,就没贴太多返回数据
可以使用python的json模块解析json,使用的函数如下:
#json字符串中{}内部的数据会解析成字典,[]会解析成列表
json.loads(jsonString)
将爬取的数据存储在excel中,使用python中的xlwt,使用的函数如下:
#相当于创建一个excel
Workbook(encoding='utf-8')
#向excel中添加名为movie_info的页
add_sheet('movie_info')
#向页的某行、某列写入某个数据,这个函数参数不止这么多,详情请参照文档
write(row,colunm,data)
我们需要有一个url的管理者,若因异常导致重新爬取,便不必在对处理过的url进行处理,考虑到需要永久记录url的状态,我们使用数据库,我将数据库的管理者角色封装了一下,由于每次爬取任务的表结构不一样,因此实例化时需要自己指定sql语句、sql参数、表名等数据:
import mysql.connector.pooling
import mysql.connector.connection
class db_manager:
#创建数据库连接池
def __init__(self,database,user,password,host,
select_url_sql,
update_status_downloads_sql,
update_status_finish_sql,
insert_db
):
self.select_url_sql=select_url_sql
self.update_status_downloads_sql=update_status_downloads_sql
self.update_status_finish_sql=update_status_finish_sql
self.insert_db=insert_db
try:
dbconfig = {
"user": user,
"password": password,
"host": host,
"port": 3306,
"database": database,
"charset": "utf8"
}
self.conpool=mysql.connector.pooling.MySQLConnectionPool(pool_name='image',pool_size=10,**dbconfig)
except Exception as arg:
print(arg)
#获取未爬取的url,将状态设置为download 记得commit
def getUrl(self):
con=self.conpool.get_connection()
cursor=con.cursor()
try:
cursor.execute(self.select_url_sql)
result=cursor.fetchone()
if result==None:
return None
sql=self.update_status_downloads_sql % result[0]
cursor.execute(sql)
con.commit()
except Exception as Arg:
con.rollback()
print(Arg)
return None
finally:
if con:
con.close()
if cursor:
cursor.close()
return result
#插入url数据,刚开始爬取时,获取img标签的url
def inserturl(self,para):
con=self.conpool.get_connection()
curson=con.cursor()
try:
curson.execute(self.insert_db%para)
con.commit()
except Exception as Arg:
print(Arg)
finally:
if con:
con.close()
if curson:
curson.close()
#爬取完毕后,将状态置为finish
def finishCrawle(self,index):
con=self.conpool.get_connection()
cursor=con.cursor()
try:
sql=self.update_status_finish_sql%index
cursor.execute(sql)
con.commit()
except Exception as Arg:
con.rollback()
print(Arg)
finally:
if con:
con.close()
if cursor:
cursor.close()
#出现异常后,将当前url的status置为new
def setStatusToNew(self,index):
con=self.conpool.get_connection()
cursor=con.cursor()
try:
sql=self.set_status_to_new%index
cursor.execute(sql)
con.commit()
except Exception as Arg:
con.rollback()
print(Arg)
finally:
if con:
con.close()
if cursor:
cursor.close()
1、影片类型在标签标签内
2、评分的html标签也具有一定的特点,其class="ll rating_num"
3、影片名字的html标签也具有一定特点
大多数情况下,网页制作人员都会做出一个前端模板出来,然后通过js等手段进行填值,因此,上述情况应该在豆瓣的大多数影片介绍的页面有效
from urllib import request
from urllib import parse
import json
from dbmanager_template import db_manager
from lxml import etree
from xlwt import Workbook
import time
#xpath使用text属性获得标签值
def getMovieUrl():
temp='豆瓣高分'
temp=parse.quote(temp)
req=request.Request('https://movie.douban.com/j/search_subjects?type=movie&tag='+temp+'&sort=rank&page_limit=250&page_start=0')
jsonString=request.urlopen(req).read().lower().decode("utf-8")
json_dict=json.loads(jsonString)
for index in range(0,len(json_dict['subjects'])):
url=json_dict['subjects'][index]['url'].replace('\\','')
dbmanager.inserturl((url))
def getMovie_info(url,row):
req=request.Request(url)
response=request.urlopen(req)
html_page=response.read().lower().decode('utf-8')
html_parse=etree.HTML(html_page)
try:
type_list=html_parse.xpath('//span[@property="v:genre"]')
comment=html_parse.xpath('//strong[@class="ll rating_num"]')[0].text
title=html_parse.xpath('//span[@property="v:itemreviewed"]')[0].text
TypeString=type_list[0].text
for index in range(1,len(type_list)):
TypeString=TypeString+' '+type_list[index].text
print(title+' '+comment+' '+TypeString)
movie_info.write(row,0,title)
movie_info.write(row,1,comment)
movie_info.write(row,2,TypeString)
return 1
except Exception as arg:
print(arg)
if 'IP' in html_page:
print("IP被禁")
return -1
else:
print(url+":元素不存在")
return 0
if __name__=='__main__':
dbmanager=db_manager(
'moviedb','root','12345','127.0.0.1',
'select indexs,url from movieinfo where status="new" limit 1 for update',
'update movieinfo set status="download" where indexs="%d"',
'update movieinfo set status="finish" where indexs="%d"',
'insert into movieinfo(url) values("%s")' ,
'update movieinfo set status="new" where indexs="%d"'
)
excel=Workbook(encoding='utf-8')
movie_info=excel.add_sheet('movie_info')
row=0
getMovieUrl()
try:
while True:
url_result=dbmanager.getUrl()
if url_result is None:
break
weatherSuccess=getMovie_info(url_result[1],row)
row=row+1
if weatherSuccess!=1:
dbmanager.setStatusToNew(url_result[0])
if weatherSuccess==-1:
break
else:
dbmanager.finishCrawle(url_result[0])
time.sleep(2)
except Exception as Arg:
print(Arg)
dbmanager.setStatusToNew(url_result[0])
excel.save('C:\\Users\\lzy\\Desktop\\movie_info.xls')
excel.save('C:\\Users\\lzy\\Desktop\\movie_info.xls')
这里需要注意几点内容:
1、http默认不支持中文,虽然我们有时看到有中文的url,但是在发送报文时,url是会进行编码的,中文会被编码成%xx的形式,如下:
https://movie.douban.com/subject/26842702/?tag=热门&from=gaia编码后变为https://movie.douban.com/subject/26842702/?tag=%E7%83%AD%E9%97%A8&from=gaia
相应的编码规则可以自己谷歌,urllib模块中,可以通过parse.quote函数将中文编码成%xx的形式
2、爬取过程中需要时刻注意自己是否被禁掉了,因为豆瓣封禁IP并不会返回http错误,而是返回一个IP封禁的页面,这个页面有一个特点,就是一定会包括IP两个字样,因此可以通过检测页面是否含有IP来判断自己是否被封禁,这里我借鉴了懒加载的思想,并没有每次都检测,被禁掉后:
comment=html_parse.xpath('//strong[@class="ll rating_num"]')[0].text
title=html_parse.xpath('//span[@property="v:itemreviewed"]')[0].text
这两句代码一定会抛异常,此时在检测是否被禁止
3、每次爬取过后一定要睡眠一段时间,做到文明爬取
爬取的文档已上传:https://download.csdn.net/download/dhaiuda/10579392