zoukankan      html  css  js  c++  java
  • 微博关键词爬虫——基于requests和aiohttp

      requests库是python爬虫中最常见的库,与内置的urllib库相比,它更加简洁高效,是每一个接触爬虫者都务必要掌握的基础;但它也是有缺点的,就是不支持异步操作,虽然可以通过多线程来解决,但当需要发送大量请求时,创建大量的线程会浪费过多的资源;此时出现了一个新的库aiohttp,它是支持异步操作的,可以在一个线程中,通过异步多任务来实现快速发送请求,提高效率。这次,我基于这两个库,做一个高效的微博关键词爬虫,源码在文章的末尾。

      首先,我是从微博的移动端地址入手,发现它是ajsx请求,请求参数中,除页码外,其他都是常量,所以要实现多页请求,直接将页码当作参数发送即可。但是页面返回的json数据并没有直接标明总页数,所以需要自己计算。进一步分析,发现数据中包含了微博的总条数和每一页的条数,这就是突破口,对它进行简单的运算就可以拿到总页码。此处只需要发送一次请求,就可以获取到信息,所以这里采用的是requests。

    def get_page():
        """
        先用requests构造请求,解析出关键词搜索出来的微博总页数
        :return: 返回每次请求需要的data参数
        """
        data_list = []
        data = {
            'containerid': '100103type=1&q={}'.format(kw),
            'page_type': 'searchall'}
        resp = requests.get(url=url, headers=headers, params=data)
        total_page = resp.json()['data']['cardlistInfo']['total']  # 微博总数
        # 一页有10条微博,用总数对10整除,余数为0则页码为总数/10,余数不为0则页码为(总数/10)+1
        if total_page % 10 == 0:
            page_num = int(total_page / 10)
        else:
            page_num = int(total_page / 10) + 1
        # 页码为1,data为当前data,页码不为1,通过for循环构建每一页的data参数
        if page_num == 1:
            data_list.append(data)
            return data_list
        else:
            for i in range(1, page_num + 1):
                data['page'] = i
                data_list.append(copy.deepcopy(data))
            return data_list
    页码解析

      获取完页码之后,就可以进行数据解析。每一页都需要单独发送请求,为了提高效率,此处采用的是aiohttp。通过async关键词来定义特殊函数,返回一个协程对象,注意函数内部所有代码必须是支持异步操作的。在构建请求的时候需要注意特定的格式。

    # async定义函数,返回一个协程对象
    async def crawl(data):
        """
        多任务异步解析页面,存储数据
        :param data: 请求所需的data参数
        :return: None
        """
        async with aiohttp.ClientSession() as f:  # 实例化一个ClientSession
            async with await f.get(url=url, headers=headers, params=data) as resp:  # 携带参数发送请求
                text = await resp.text()  # await 等待知道获取完整数据
                text_dict = json.loads(text)['data']['cards']
                parse_dict = {}
                for card in text_dict:
                    if card['card_type'] == 9:
                        scheme = card['scheme']
                        if card['mblog']['isLongText'] is False:
                            text = card['mblog']['text']
                            text = re.sub(r'<.*?>|
    +', '', text)
                        else:
                            text = card['mblog']['longText']['longTextContent']
                        user = card['mblog']['user']['profile_url']
                        comments_count = card['mblog']['comments_count']
                        attitudes_count = card['mblog']['attitudes_count']
                        parse_dict['url'] = scheme
                        parse_dict['text'] = text
                        parse_dict['author'] = user
                        parse_dict['comments_count'] = comments_count
                        parse_dict['attitudes_count'] = attitudes_count
                        parse_dict_list.append(copy.deepcopy(parse_dict))
    数据解析

      最关键的一步,将协程对象添加到事件循环中,实现异步执行。

    task_list = []  # 定义一个任务列表
        for data in data_list:
            c = crawl(data)  # 调用协程,传参
            task = asyncio.ensure_future(c)  # 创建任务对象
            task_list.append(task)  # 将任务添加到列表中
        loop = asyncio.get_event_loop()  # 创建事件循环
        loop.run_until_complete(asyncio.wait(task_list))  # 开启循环,并将阻塞的任务挂起
    事件循环

      以上部分就是整个爬虫的关键,剩下的数据写入(导出到excle)就直接放在源码中,不足之处,请大家指正!

    import copy
    import aiohttp
    import requests
    import re
    import asyncio
    import json
    import xlwt
    
    
    def get_page():
        """
        先用requests构造请求,解析出关键词搜索出来的微博总页数
        :return: 返回每次请求需要的data参数
        """
        data_list = []
        data = {
            'containerid': '100103type=1&q={}'.format(kw),
            'page_type': 'searchall'}
        resp = requests.get(url=url, headers=headers, params=data)
        total_page = resp.json()['data']['cardlistInfo']['total']  # 微博总数
        # 一页有10条微博,用总数对10整除,余数为0则页码为总数/10,余数不为0则页码为(总数/10)+1
        if total_page % 10 == 0:
            page_num = int(total_page / 10)
        else:
            page_num = int(total_page / 10) + 1
        # 页码为1,data为当前data,页码不为1,通过for循环构建每一页的data参数
        if page_num == 1:
            data_list.append(data)
            return data_list
        else:
            for i in range(1, page_num + 1):
                data['page'] = i
                data_list.append(copy.deepcopy(data))
            return data_list
    
    
    # async定义函数,返回一个协程对象
    async def crawl(data):
        """
        多任务异步解析页面,存储数据
        :param data: 请求所需的data参数
        :return: None
        """
        async with aiohttp.ClientSession() as f:  # 实例化一个ClientSession
            async with await f.get(url=url, headers=headers, params=data) as resp:  # 携带参数发送请求
                text = await resp.text()  # await 等待知道获取完整数据
                text_dict = json.loads(text)['data']['cards']
                parse_dict = {}
                for card in text_dict:
                    if card['card_type'] == 9:
                        scheme = card['scheme']
                        if card['mblog']['isLongText'] is False:
                            text = card['mblog']['text']
                            text = re.sub(r'<.*?>|
    +', '', text)
                        else:
                            text = card['mblog']['longText']['longTextContent']
                        user = card['mblog']['user']['profile_url']
                        comments_count = card['mblog']['comments_count']
                        attitudes_count = card['mblog']['attitudes_count']
                        parse_dict['url'] = scheme
                        parse_dict['text'] = text
                        parse_dict['author'] = user
                        parse_dict['comments_count'] = comments_count
                        parse_dict['attitudes_count'] = attitudes_count
                        parse_dict_list.append(copy.deepcopy(parse_dict))
    
    
    def insert_data(file_name):
        """
        将数据导出到excle中
        :param file_name: 文件名
        :return:
        """
        wr = xlwt.Workbook(encoding='utf8')
        table = wr.add_sheet(file_name)
        table.write(0, 0, '原链接')
        table.write(0, 1, '正文')
        table.write(0, 2, '作者首页')
        table.write(0, 3, '评论数')
        table.write(0, 4, '点赞数')
        for index, data in enumerate(parse_dict_list):
            table.write(index + 1, 0, data['url'])
            table.write(index + 1, 1, data['text'])
            table.write(index + 1, 2, data['author'])
            table.write(index + 1, 3, data['comments_count'])
            table.write(index + 1, 4, data['attitudes_count'])
        file_path = file_name + '.xls'
        wr.save(file_path)
    
    
    def main(file_name):
        """
        开启多任务循环
        :return: None
        """
        data_list = get_page()  # 接收data参数列表
        task_list = []  # 定义一个任务列表
        for data in data_list:
            c = crawl(data)  # 调用协程,传参
            task = asyncio.ensure_future(c)  # 创建任务对象
            task_list.append(task)  # 将任务添加到列表中
        loop = asyncio.get_event_loop()  # 创建事件循环
        loop.run_until_complete(asyncio.wait(task_list))  # 开启循环,并将阻塞的任务挂起
        insert_data(file_name)
    
    
    if __name__ == '__main__':
        kw = input('关键词:')
        headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2'}
        url = 'https://m.weibo.cn/api/container/getIndex'
        parse_dict_list = []  # 临时存放爬取的数据
        main(kw)
    完整代码

      注意,由于微博的反爬机制,每次短时间的大量请求都会导致ip被短时间禁用,此处可以通过添加代理的方式来解决。我的想法是在页码解析部分添加代理池,随机选择代理,如果当前ip返回的状态码为200,则进行解析出页码,并将该ip携带到页面解析;若状态码不是200,则循环选择下一个ip。

  • 相关阅读:
    springmvc乱码问题
    51nod 还是01串
    51nod 1276 岛屿的数量
    poj 2486 a apple tree
    hdu 1011 Starship Troopers
    poj 1155 TELE
    hdu 4586 Play the Dice
    hdu 5023 A Corrupt Mayor's Performance Art(线段树水题)
    Appleman and Tree
    hdu 4003
  • 原文地址:https://www.cnblogs.com/xtjiaoyou/p/12544575.html
Copyright © 2011-2022 走看看