• 热门专题

Python初学者之网络爬虫(二)

作者:octans  发布日期:2016-12-14 20:33:11
Tag标签:爬虫  初学者  网络  
  • 声明:本文内容和涉及到的代码仅限于个人学习,任何人不得作为商业用途。转载请附上此文章地址

    本篇文章Python初学者之网络爬虫的继续,最新代码已提交到https://github.com/octans/PythonPractice

    1. 上篇回顾

    上篇文章Python初学者之网络爬虫中我从花椒的热门推荐页面入手,进而获取到主播个人信息和对应的直播历史视频。

    首先看一下上一篇文章中对huajiao.com的主播和视频的爬取成果:

    # getUserCount
    10179
    # getLiveCount
    111574
    到目前已收集了10179个主播信息,和这些主播的111574个视频信息。这里数据量小的原因是我只收集了花椒热门推荐下面的主播,这个页面每次展示60个系统推荐的主播。  

    到目前为止我新做了如下事情:

    对MySql的读写操作进行了封装 编码风格遵从PEP8 爬取沃米优选网(http://video.51wom.com/)的主播信息 爬取一下网(http://www.yixia.com/)的主播信息和视频信息

    其中对MySql的封装代码单独放到了文件mysql.py下,做为一个module使用,这个module虽然简单,但已经实现了select,insert,delete等操作,对MySql封装感兴趣的同学可以参考, 但请不要用于生产环境。推荐去使用和阅读数据库类peewee。
    接下来将继续讲述我在数据抓取上的开发经历。

    2. 爬取的数据源和逻辑

    最终目标:收集到各大直播平台的主播信息和历史播放记录,进而对数据进行聚合分析。
    当前已完成:对花椒网的数据收集。
    沃米优选网(http://video.51wom.com/)是一个网红数据聚合的网站,它收集了各个直播平台(花椒,熊猫,秒拍,斗鱼,映客,一直播,美拍)的热门主播信息。所以我希望能从它这里获取到各个平台的热门主播信息,之后拿着主播id去对应的直播平台去爬取更详细的信息。

    3. 爬取沃米优选网的主播列表页

    列表页http://video.51wom.com/截图如下:
    qq20161211-2251292x
    初看这是一个列表页,并且底部有分页链接,点击分页时触发表单提交

    3.1 分析结论和构思程序逻辑

    当点击底部分页时,使用chrom开发者工具,看到有XHR请求如下截图:
    qq20161211-2318152x

    从截图和一些测试可以分析出:

    a) 要请求第二页以后的数据,需要将相应的cookie和csrf数据提交给网站; b) 提交的方式是POST的”multipart/form-data”; c) 提交的参数有_csrf, stage-name, platform, industry等; d) 请求的返回结果是一个表格列表的html代码;

    对于cookie容易拿到,但_csrf如何获取呢?
    查看页面源码,发现网站在生成列表页时已经将csrf的值写入了表单;同一个csrf值在后续请求中可以多次使用

    <input type='hidden' name='_csrf' value='aWF6ZGMzclc9EAwRK3Y4LhobNQo6eEAdWwA0IFd1ByUDNTgwClUEZw=='>

    由以上分析,程序的逻辑应该这样,

    a) 先请求主播列表的首页,获取到csrf值和cookie b) 将csrf和cookie值保存,用于下次请求 c) 请求主播列表的第二页,第三页等 d) 将获取到的表格列表的html代码使用BeautifulSoup进行解析,遍历每个行,行里的每个列 e) 将获取到的数据写入mysql

    3.2 python编码获取沃米优选网的主播信息

    a) 构造基础类class Website, 之后为每个网站建立一个class,继承Website

    有些请求返回的是html代码,类里设置好html解析器; 有些请求返回的是json串,基类里设置好json的解析器; 请求每个网站时,需要设置不同的header,将header放在基类; 对post的Content-Type:multipart/form-data方式进行函数封装; 对post的Content-Type: application/x-www-form-urlencoded方式分别进行函数封装; 这里面尽量把各种不同的请求方式写成函数,而不使用type参数的形式,便于子类清晰的调用;

    注意以下代码为了节省篇幅,不是完整代码,也非PEP8代码规范

    class Website:
        ### 使用requests.session()能够自动处理cookies
        session = requests.session()
    
        ### 设置html解析器
        htmlParser = BeautifulSoup
    
        ### 设置json解析器
        jsonParser = json
    
        ### 设置headers
        headers = {
            'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/'
                          '54.0.2840.98 Safari/537.36'
        }
        ### 直接发起get请求
        def get(self, url, params=None):
            if params is None:
                params = {}
            return self.session.get(url, params=params, headers=self.headers)
    
        ### 发起get请求并返回解析后的html对象
        def get_html(self, url, params=None):
            r = self.get(url, params)
            return self.htmlParser(r.text, 'html.parser')
    
        ### 发起get请求并返回解析后的json对象
        def get_json(self, url, params=None):
            r = self.get(url, params)
            return self.jsonParser.loads(r.text)
    
        ### 发起post请求,以Content-Type:multipart/form-data方式
        def post_multi_part(self, url, params):
            kwargs = dict()
            for (k, v) in params.items():
                kwargs.setdefault(k, (None, v))
            r = self.session.post(url, files=kwargs, headers=self.headers)
            return self.htmlParser(r.text, 'html.parser')

    b) 构造class WoMiYouXuan, 封装对网站沃米优选的请求

    方法first_kiss()用于第一次请求网站,获取到csrf值由属性self.csrf保存; first_kiss()另一个作用是获取到cookie,虽然没有显示处理,因为requests.session()帮我们处理了,自动获取自动提交; 注意在一个实例里,只需调用一次first_kiss()即可,之后就可以多次调用其他的页面请求函数了; csrf和cookie是由关联的,网站会校验,都要提交; 方法parse_actor_list_page()是具体分析主播的列表html代码,这是一个细致活; 方法spider_actors是骨架函数,循环访问每个分页数据并将结果写入mysql;
    class WoMiYouXuan(Website):
        ### 发起post请求时需要将csrf发给网站
        csrf = ''
        def __init__(self):
            self.first_kiss()
    
        ### 首次访问该网站获取到csrf值并保存到self.csrf, 供其他post请求直接使用
        def first_kiss(self):
            url = 'http://video.51wom.com/'
            html = self.get_html(url)
            self.csrf = html.find('meta', {'name': 'csrf-token'}).attrs['content']
        
        ### 从主播列表页获取主播信息
        def parse_actor_list_page(self, page=1):
            ### 构造参数->发起post请求
            url = 'http://video.51wom.com/media/' + str(page) + '.html'
            keys = ('_csrf', 'stage-name', 'platform', ' industry', 'price', 'follower_num', 'follower_area',
                    'page', 'is_video_platform', 'sort_by_price', 'type_by_price')
            params = dict()
            for key in keys:
                params.setdefault(key, '')
            params['_csrf'] = self.csrf
            params['page'] = str(page)
            html = self.post_multi_part(url, params)
            
            ### 解析主播列表
            trs = html.find('div', {'id': 'table-list'}).table.findAll('tr')
            trs.pop(0)  # 去除标题行
            actor_list = list()
            for tr in trs:
                ### 后面太多了,有兴趣的同学去看源码吧
    
        ### 骨架函数,循环访问每个分页数据并将结果写入mysql
        def spider_actors(self):
            page = 1
            tbl_actor = WMYXActor()
            while True:
                ret = self.parse_actor_list_page(page)
                for actor in ret['items']:
                    actor['price_dict'] = json.dumps(actor['price_dict'])
                    tbl_actor.insert(actor, replace=True)
                if ret['items_count'] * ret['page'] < ret['total']:
                    page += 1
                else:
                    break

    方法parse_actor_list_page()具体分析主播列表的html代码,这是一个细致活;感受一下代码截图
    qq20161211-2353172x

    3.3 知识点总结

    a) 表单提交的POST方式
    通常只提交一些kv数据时,使用application/x-www-form-urlencoded方式;
    通常上传文件时,使用multipart/form-data方式,但此种方式也是可以提交kv类数据的,比如上面的获取主播列表数据时就是使用此方式。
    b) Python的网络请求库Requests
    这个库太好用了!并且能够对cookie自动处理,比如我在基类Website中的使用方式; 并且使用它构造multipart/form-data方式的post请求也很方便,比如方法Website::post_multi_part()
    c) Python中使用正则匹配字符串中的整数,如下代码:

    avg_watched = tds[6].get_text(strip=True)  # 平均观看人数
    mode = re.compile(r'd+')
    tmp = mode.findall(avg_watched)

    d) 使用try, except机制来实现类似php里的isset(),如下代码:

    # 判断是否有逗号,比如8,189
    try:
       index = string.index(',')
       string = string.replace(',', '')
    except ValueError:
       string = string

    e) 一定要注意python中的’1’和1是不一样的,需要你自己来做字符串和数字的类型转换

    4. 爬取秒拍网的主播和视频信息

    在沃米优选网拿到了各个直播平台的主播id, 先实现对一下网(http://www.yixia.com/)的抓取,获取对应的主播和视频信息。
    一下网的个人主页地址为http://www.yixia.com/u/uid, 这个uid就是主播id, 如下截图:
    qq20161212-0019252x

    4.1 分析结论和构思程序逻辑

    a) 在主播个人主页能够拿到主播的个人信息,如头像,昵称,粉丝数等,还能拿到主播的视频列表; b) 视频列表的加载方式是瀑布流方式,意味着走的是ajax接口; c) 视频列表接口返回的数据是html代码,仍然需要用BeautifulSoup解析; d) 请求视频列表接口时需要提交suid参数,这个参数值需要用uid在主播个人页获取;

    4.2 python编码一下网的主播信息和视频列表

    构造class YiXia(Website), 方法parse_user_page()拿着uid去获取主播个人信息; 方法get_video_list()按分页获取视频列表数据
    class YiXia(Website):
        ### 访问主播页面,也是视频列表页,从该页面获取到suid和主播个人信息
        def parse_user_page(self, uid):
            print(self.__class__.__name__ + ':parse_user_page, uid=' + uid)
            user = dict()
            user['uid'] = uid
            url = 'http://www.yixia.com/u/' + uid
            bs = self.get_html(url)
    
            div = bs.find('div', {'class': 'box1'})
            user['nickname'] = div.h3.a.get_text(strip=True)  # 昵称
    
            stat = div.ol.get_text(strip=True)
            stat = re.split('关注||粉丝', stat)
            user['follow'] = stat[0].strip()  # 关注数
            user['followed'] = stat[1].strip()  # 粉丝数
            ### ------这里省略很多代码----
    
            return user
    
        ### AJAX请求视频列表
        def get_video_list(self, suid, page=1):
            url = 'http://www.yixia.com/gu/u'
            payload = {
                'page': page,
                'suid': suid,
                'fen_type': 'channel'
            }
            json_obj = self.get_json(url, params=payload)
            msg = json_obj['msg']
            msg = BeautifulSoup(msg, 'html.parser')
    
            ### 解析视频标题
            titles = list()
            ps = msg.findAll('p')
            for p in ps:
                titles.append(p.get_text(strip=True))  # 视频标题
    
            ### 解析视频赞和评论数
            stats = list()
            divs = msg.findAll('div', {'class': 'list clearfix'})
            for div in divs:
                tmp = div.ol.get_text(strip=True)
                tmp = re.split('赞||评论', tmp)
                stats.append(tmp)
    
            ### 解析视频其他数据
            videos = list()
            divs = msg.findAll('div', {'class': 'D_video'})
            for (k, div) in enumerate(divs):
                video = dict()
                video['scid'] = div.attrs['data-scid']
    
            ### ------这里省略很多代码------
    
            return videos
    
        ### 骨架函数,获取每个视频的每个分页数据
        def spider_videos(self, suid, video_count):
            page = 1
            current = 0
            tbl_video = YiXiaVideo()
            while current < int(video_count):
                print('spider_videos: suid=' + suid + ', page=' + str(page))
                videos = self.get_video_list(suid, page)
                for video in videos:
                    tbl_video.insert(video, replace=True)
                current += len(videos)
                page += 1
            return True

    4.3 知识点总结

    大部分还是3.3里的知识点,这里重点注意字符串和整形,浮点型数字的转换。比如粉丝数’2.3万’是一个字符串,需要转成浮点数2.3或者整数23000;再比如’8,189’需要转成8189.

    5. 程序结果

    以下截图为采集到的一下网视频数据:
    qq20161212-0102152x

    6. 知识点参考

    这里列出我记录下来的参考链接:

    解析json: JSON encoder and decoder 解析html: BeautifulSoup 构造http请求:Requests 文件操作:Reading and Writing Files 数据库的ORM库: peewee 对字符串的操作:Python 字符串操作 对list的操作:More on Lists|Python 列表(List)操作方法详解 对dict的操作:Dictionaries|Python中dict详解 对True和Flase的使用:Python的Boolean操作 POST提交方式:四种常见的 POST 提交数据方式 Python编码风格:PEP8

    qrcode_for_gh_61c6224cfae9_258

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规