Python爬虫笔记(四)——动态页面的处理(上篇)

什么是动态页面

我们知道js可以操纵DOM,可以请求后台,因此我们最终看到的html页面可能是js执行的结果,如果我们单纯用爬虫获取动态页面的html,看到的可能就是一堆js

动态页面的处理

我自己总结了两种方式,1、获取后台接口,2、通过selenium+phantomjs,这篇博客先介绍第一种,这两种方式各有优劣

方式一:

动态页面有一个特点,它所需要的数据需要自己去请求后台,不是写死在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进行处理,考虑到需要永久记录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、影片类型在标签<span property="v:genre"><span/>标签内

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

猜你喜欢

转载自blog.csdn.net/dhaiuda/article/details/81348573
今日推荐