Python爬虫【一】爬取移动版“微博辟谣”账号内容(API接口)_微博半年可见爬虫-程序员宅基地

技术标签: 爬虫  python  微博  数据采集  爬取微博内容(学习用)  

专题系列导引

  爬虫课题描述可见:

Python爬虫【零】课题介绍 – 对“微博辟谣”账号的历史微博进行数据采集

  课题解决方法:

微博移动版爬虫

Python爬虫【一】爬取移动版“微博辟谣”账号内容(API接口)

微博PC网页版爬虫

Python爬虫【二】爬取PC网页版“微博辟谣”账号内容(selenium同步单线程)
Python爬虫【三】爬取PC网页版“微博辟谣”账号内容(selenium单页面内多线程爬取内容)
Python爬虫【四】爬取PC网页版“微博辟谣”账号内容(selenium多线程异步处理多页面)



前言

  本文我们针对3G4G移动版微博网站(URL: https://m.weibo.cn),爬取"微博辟谣"的数据

一. 分析

微博辟谣的URL地址:

https://m.weibo.cn/u/1866405545?uid=1866405545&t=0&luicode=10000011&lfid=100103

微博页面效果:

在这里插入图片描述

爬取思路

  首先对微博辟谣网页访问和翻页时进行数据抓包,发现调用了如下接口:
在这里插入图片描述
接口返回数据集:
在这里插入图片描述

因此总结特点如下:

  1. 移动版网站页面是通过js访问API接口、再渲染到html元素的方式来加载内容的。因此我们可以不管html页面展示如何,而采用直接调用后台API接口来提取数据
  2. API接口中通过添加入参page=?的方式来实现不同页面数据内容的获取;新版API(2021年11月)换用了since_id=?作为入参,但也保留了page参数,两者都可实现分批按页查询的功能。since_id参数说明如下:每次API请求,返回的结果中都会保留下一页查询需要的since_id值;当翻页时,下一次API调用传入的since_id即为此since_id。所以我们可以通过更换此参数不断的实现分页爬取

二. 处理流程

  整个微博移动版的爬取流程,可总结为以下四步:

1. 创建用来保存数据的DataFrame对象:excel_df
2. 从第一页开始,访问当前页面的API接口,获取微博数据并提取相关字段,存入df中
3. 不断的向后请求每一页的API接口,重复上面的提取数据和存df操作,直到最后一页
4. 将整个excel_df数据写入excel中

三. 代码实现

1. 项目结构

  Python爬虫工程使用requests模块请求API接口。因为整体功能比较简单,所以使用面向过程的设计模式,用函数调用串联整个业务。
  工程结构如下:

  • 创建m_crawler.py模块,定义爬取流程所需要的几个函数

以上处理模块,放在名为m包中

  • 因为DataFrame数据写excel比较基础,所以我们将它设计为一个工具方法。定义一个util.py模块,将工具方法都写入此模块中
  • 项目中URL、写入地址、表头等配置变量比较多,因此将他们写入property.py文件

以上两个公共模块,放在名为common的包中

  • 在项目下创建一个名为excel的文件夹,将最终的导出结果文件存于其中
  • main.py作为整个项目的启动入口

最终项目结构如下图:
在这里插入图片描述

2. main.py

  main.py为程序入口,启动工程时首先从这里开始执行。因为我们的串联类为CrawlHandle,并且需要Crawler做为入参,因此main.py中代码设计如下:

if __name__ == '__main__':
    # 移动版微博爬取
    m_crawler.crawler_m_weibo_write_excel()
3. m_crawler.py模块
1. crawler_m_weibo_write_excel()函数

  def crawler_m_weibo_write_excel()为爬取处理主函数,根据上面的设计,功能是串联整个爬取的流程,设计如下:


def crawler_m_weibo_write_excel():
    """
    主方法:从m移动端微博中读取数据,整理并存入指定excel
    :return:
    """
    # 定义空df,以装载处理完的数据
    excel_df = DataFrame(columns=EXCEL_COLUMNS)
    try:
        # 无线循环不断向后翻页查询,直到查至微博最后一页
        # 发现问题:移动版微博设置了只能拉取2000条数据,超过200条数据,since_id不会再返回;入参用page传参也是如此。此问题已在页面上尝试,m微博确实只能下拉2000条数据
        page = 1  # 页面计数
        while True:
            # 1. 爬取微博数据
            json_cards = get_weibo_info_by_page(page)
            # 爬不到时退出爬取循环
            if len(json_cards) == 0:
                print("已爬取到微博最后一页,退出爬虫循环...")
                break
            # 2. 解析页面返回的json数据 整理为特定格式的list并返回
            parse_json_list = list(parse_result_2_list(json_cards))
            print("爬取第 %s 页,爬取有效微博数: %s" % (page, len(parse_json_list)))

            # 3. 将此轮爬取的微博数据添加到df数据表末端
            excel_df = excel_df.append(parse_json_list)
            # 页数加1
            page += 1
            # 最好让程序睡眠一段时间,防止微博服务器认为在恶意爬取,封禁IP
            time.sleep(0.5)
    except Exception as e:
        print("爬虫程序报错,可能出现问题,请检查!", e)

    # 4. 爬取完所有数据后,进行写文档操作
    util.write_excel(excel_df, M_WB_EXCEL_PATH)

  那么接下来的重点就是补充完这五部分的方法代码细节,定义相关的类或者方法

2. get_weibo_info_by_page()函数

上面调用的"分页抓取微博数据"功能,封装了函数def get_weibo_info_by_page(),编写如下:

def get_weibo_info_by_page(page):
    """
    分页抓取微博数据
    :return:
    """

    # 拼接接口入参
    m_wb_request_params['page'] = page
    # 拼装完整的请求URL
    url = M_WB_URL + urlencode(m_wb_request_params)
    # print("请求URL: %s" % url)

    try:
        # get请求接口,返回结果为json结构数据
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            # 打印返回值,用于调试
            # print("请求得到的response==%s" % response.text)
            # 转json
            response_json = response.json()
            # 取json中的cards, 为array_list
            cards = response_json.get('data').get('cards')
            # print("提取到的cards属性:%s" % cards)
            return cards
    except requests.ConnectionError as e:
        print('爬取错误', e.args)
3. parse_result_2_list()函数

  将API请求的结果json提取必要的字段,封装入df的逻辑,封装为函数def parse_result_2_list()

def parse_result_2_list(json_cards):
    """
    解析页面返回的json数据 整理为特定格式的list并返回
    :return:
    """

    # 循环所有cards,依次处理
    for card in json_cards:
        # card_type == 9 为微博正文内容,取出分析
        if card.get('card_type') == 9:
            # 取mblog
            mblog = card.get('mblog')

            # “微博辟谣”账号发微博时输入的文字内容
            # wb_text = mblog.get('raw_text')   # 老方法,2021年已不可用
            # 新方法
            bs = BeautifulSoup(mblog.get('text'), "html.parser")
            wb_text = bs.get_text()
            
            # 剔除月度工作报告信息
            if '月度工作报告' in wb_text:
                # 剔除月度工作报告,可打印日志分析剔除结果,以防有误判删除掉有用信息
                print("剔除月度报告: %s" % mblog.get('raw_text'))
                continue

            # card转换整理后的json结果
            etl_json = {
    }

            # "微博辟谣"此条微博的id
            wb_id = mblog.get("id")
            # 微博名,这里为“微博辟谣”
            wb_name = mblog.get('user').get("screen_name")
            # “微博辟谣”账号发微博时微博时间
            wb_time = util.parse_time(mblog.get('created_at'))
            # 本微博转发数,若为文章“转发”,则说明还没人转,设为0
            wb_repost_count = mblog.get('reposts_count')
            # set值
            etl_json['WB_id'] = wb_id
            etl_json['WB_name'] = wb_name
            etl_json['WB_text'] = wb_text
            etl_json['WB_time'] = wb_time
            etl_json['WB_repost_count'] = wb_repost_count

            # mblog中若有属性 retweeted_status 说明是转发
            if mblog.get('retweeted_status'):
                weibo_info = mblog.get('retweeted_status')
                """ 当出现特殊情况无法爬取内容时的处理办法,目前处理办法是:忽略此条微博,跳过 """
                # 被设为不可见,因此无法爬取(包括原作者设为自己可见、半年内可见等情况)
                if weibo_info['visible']['type'] != 0 or weibo_info['visible']['list_id'] != 0:
                    print("以下博客已被设为不可见,不可爬取:%s" % weibo_info)
                    continue
                # 微博账号被删除z包括自己注销、被查封注销等情况)
                if 'deleted' in weibo_info:
                    print("以下博客已被删除,不可爬取:%s" % weibo_info)
                    continue

                # 原微博的id
                wb_id_org = weibo_info.get("id")
                # 原微博号名称
                wb_name_org = weibo_info.get('user').get("screen_name")
                # 原账号发微博时微博时间
                wb_time_org = util.parse_time(weibo_info.get('created_at'))
                # 原微博转发数
                wb_repost_count_org = weibo_info.get('reposts_count')

                etl_json['WB_id_org'] = wb_id_org
                etl_json['WB_name_org'] = wb_name_org
                etl_json['WB_time_org'] = wb_time_org
                etl_json['WB_repost_count_org'] = wb_repost_count_org

                etl_json['type'] = "转发"
                etl_json['weibo_name'] = wb_name_org
                etl_json['time'] = wb_time_org
                etl_json['repost_count'] = wb_repost_count_org

                # 原微博发微时输入的文字内容
                wb_text_org = weibo_info.get('raw_text')
                # 如果文章没有全部展开,则再次提取微博数据
                if ">全文<" in weibo_info.get("text"):
                    wb_long_text_org = util.get_weibo_long_text(wb_id_org)
                    # etl_json['wb_long_text_org'] = wb_long_text_org
                    # 替换文本
                    if wb_long_text_org is not None or wb_long_text_org != "":
                        wb_text_org = wb_long_text_org
                etl_json['WB_text_org'] = wb_text_org
                etl_json['text'] = wb_text_org

            else:
                etl_json['type'] = "原创"
                etl_json['weibo_name'] = wb_name
                etl_json['text'] = wb_text
                etl_json['time'] = wb_time
                etl_json['repost_count'] = wb_repost_count

            yield etl_json

因为每个API接口会请求多条微博数据,因此入参是个list; 函数中用yield关键字返回一个迭代,但我们需要的是类型为list的结果,所以调用时,直接用list()做转换,就可得到此函数迭代执行后的结果list。

4. util.py模块

此模块为工具模块,用到的方法有


def get_weibo_long_text(id):
    """
    通过id获取微博长文本
    :return:
    """

    # 拼装完整的请求URL
    url = M_WB_LONG_TEXT_URL % id
    # print("长文本请求URL: %s" % url)

    try:
        # get请求接口,返回结果为json结构数据
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            # 打印返回值,用于调试
            # print("长文本请求得到的response: %s" % response.text)
            # 转json
            response_json = response.json()
            # 提取长文本
            long_text = response_json.get('data').get('longTextContent')
            # print("提取到的long_text:%s" % long_text)

            # 将长文本用bs4转换为纯文字
            html_soup = BeautifulSoup(long_text)
            text = html_soup.get_text()

            if text:
                pass
            else:
                print("!调用接口查询长文本出错! URL = %s ,response= %s" % (url, response.text))
            return text
    except Exception as e:
        print('爬取长文本错误')
        traceback.print_exc()


def write_excel(excel_df, excel_path):
    """
    将结果写入Excel
    :param excel_df:
    :param excel_path:
    :return:
    """

    print("开始写入Excel文档:文档名称 %s" % excel_path)
    excel_df.to_excel(excel_path, index=False)
    # 如果只想要前四列,则用下面的语句:
    # excel_df[excel_columns[:4]].to_excel(excel_path, index=False)
    print("写入Excel文档成功!")

主要是用来请求全文,还有些Excel的工具方法

结束

  以上整个爬虫项目编写完毕,只有配置变量和用到的工具方法没有贴出,具体实现可见源代码

四. 爬虫执行

1. 执行过程

当程序执行后,可以看到依次爬取每页微博数据,一页中有25条数据,会空过已删除、不可见、月报告的微博
在这里插入图片描述

2. 执行结果

    最后爬取存入excel的结果如下,前四列即为课题要求的结果。
日期笔者在2020年12月时为yyyy-MM-dd的格式,2021年3月19日重试时,返回结果改为了日期字符串,这里可以用日期转换工具类做转换,format日期值,博主此处省略
在这里插入图片描述

五. 问题总结

  1. 为了防止过快访问被微博服务器检测到恶意爬取,最好每次调用完成处理后,等待1s左右的时间(如果能用ip池做代理绕开服务器爬取检测更好)
  2. 移动版微博API设置了只能拉取2000条数据(前80页)。超过2000条数据,since_id不会再返回;接口入参用page传参也是如此,因此数据不全问题无法回避。
  3. 上述问题的解决办法只能更改数据爬取渠道,使用爬取PC网页版来完成全量数据爬取目标。在后面的【二】【三】【四】中即可看到实现方式
  4. 因为爬取移动版微博是调用API接口,所以速度快,稳定性好,可靠性高。每页微博25条数据大概平均用时6秒,总共80页数据用时480秒,约8分钟。折合到微博辟谣45*240条的总量(通过PC版统计),估计全量爬取用时大约0.7小时
  5. 微博API接口和页面格式、字段、日期表示一直有变化,博主程序满足当前(2021年3月)的微博设计情况;若有变化读者需要根据实际情况进行改造

执行程序

项目工程编译了windows版本执行程序:微博数据采集python+selenium执行程序:WBCrawler.exe

  1. 执行项目前,需要下载selenium对应的浏览器驱动程序(driver.exe),并放在本机环境变量路径中,否则会报错。安装操作具体可见博客专题中的指导【二】

  2. 执行程序时,会在系统用户默认路径下,创建一个虚拟的python环境(我的路径是C:\Users\Albert\AppData\Local\Temp_MEI124882\),因此启动项目所需时间较长(约20秒后屏幕才有反应,打出提示),请耐心等待;也正因如此,执行电脑本身环境是可以无需安装python和selenium依赖包的;同时最后爬取保存的excel也在此文件夹下。

  3. 本项目采用cmd交互方式执行,因此等到屏幕显示:

     选择爬取方式:
     1. 移动版微博爬取
     2. PC网页版微博爬取(单线程)
     3. PC网页版微博爬取(页面内多线程)
     4. PC网页版微博爬取(多线程异步处理多页面)
    

后,用键盘输入1~4,敲回车执行

  1. 此exe编译时,工程代码内编写的最终excel记录保存地址为:相对工程根路径下的excel文件夹;因此当本exe执行到最后保存数据时,会因为此excel文件夹路径不存在而报错。若在工程中将保存地址改为绝对路径(例如D:\excel\),再编译生成exe执行,则最终爬取数据可以正确保存

项目工程

工程参见:微博数据采集python+selenium工程:WBCrawler.zip

本专题内对源码粘贴和分析已经比较全面和清楚了,可以满足读者基本的学习要求。源码资源为抛砖引玉,也只是多了配置文件和一些工具方法而已,仅为赶时间速成的同学提供完整的项目案例。大家按需选择


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010086122/article/details/114997159

智能推荐

艾美捷Epigentek DNA样品的超声能量处理方案-程序员宅基地

文章浏览阅读15次。空化气泡的大小和相应的空化能量可以通过调整完全标度的振幅水平来操纵和数字控制。通过强调超声技术中的更高通量处理和防止样品污染,Epigentek EpiSonic超声仪可以轻松集成到现有的实验室工作流程中,并且特别适合与表观遗传学和下一代应用的兼容性。Epigentek的EpiSonic已成为一种有效的剪切设备,用于在染色质免疫沉淀技术中制备染色质样品,以及用于下一代测序平台的DNA文库制备。该装置的经济性及其多重样品的能力使其成为每个实验室拥有的经济高效的工具,而不仅仅是核心设施。

11、合宙Air模块Luat开发:通过http协议获取天气信息_合宙获取天气-程序员宅基地

文章浏览阅读4.2k次,点赞3次,收藏14次。目录点击这里查看所有博文  本系列博客,理论上适用于合宙的Air202、Air268、Air720x、Air720S以及最近发布的Air720U(我还没拿到样机,应该也能支持)。  先不管支不支持,如果你用的是合宙的模块,那都不妨一试,也许会有意外收获。  我使用的是Air720SL模块,如果在其他模块上不能用,那就是底层core固件暂时还没有支持,这里的代码是没有问题的。例程仅供参考!..._合宙获取天气

EasyMesh和802.11s对比-程序员宅基地

文章浏览阅读7.7k次,点赞2次,收藏41次。1 关于meshMesh的意思是网状物,以前读书的时候,在自动化领域有传感器自组网,zigbee、蓝牙等无线方式实现各个网络节点消息通信,通过各种算法,保证整个网络中所有节点信息能经过多跳最终传递到目的地,用于数据采集。十多年过去了,在无线路由器领域又把这个mesh概念翻炒了一下,各大品牌都推出了mesh路由器,大多数是3个为一组,实现在面积较大的住宅里,增强wifi覆盖范围,智能在多热点之间切换,提升上网体验。因为节点基本上在3个以内,所以mesh的算法不必太复杂,组网形式比较简单。各厂家都自定义了组_802.11s

线程的几种状态_线程状态-程序员宅基地

文章浏览阅读5.2k次,点赞8次,收藏21次。线程的几种状态_线程状态

stack的常见用法详解_stack函数用法-程序员宅基地

文章浏览阅读4.2w次,点赞124次,收藏688次。stack翻译为栈,是STL中实现的一个后进先出的容器。要使用 stack,应先添加头文件include<stack>,并在头文件下面加上“ using namespacestd;"1. stack的定义其定义的写法和其他STL容器相同, typename可以任意基本数据类型或容器:stack<typename> name;2. stack容器内元素的访问..._stack函数用法

2018.11.16javascript课上随笔(DOM)-程序员宅基地

文章浏览阅读71次。<li> <a href = "“#”>-</a></li><li>子节点:文本节点(回车),元素节点,文本节点。不同节点树:  节点(各种类型节点)childNodes:返回子节点的所有子节点的集合,包含任何类型、元素节点(元素类型节点):child。node.getAttribute(at...

随便推点

layui.extend的一点知识 第三方模块base 路径_layui extend-程序员宅基地

文章浏览阅读3.4k次。//config的设置是全局的layui.config({ base: '/res/js/' //假设这是你存放拓展模块的根目录}).extend({ //设定模块别名 mymod: 'mymod' //如果 mymod.js 是在根目录,也可以不用设定别名 ,mod1: 'admin/mod1' //相对于上述 base 目录的子目录}); //你也可以忽略 base 设定的根目录,直接在 extend 指定路径(主要:该功能为 layui 2.2.0 新增)layui.exten_layui extend

5G云计算:5G网络的分层思想_5g分层结构-程序员宅基地

文章浏览阅读3.2k次,点赞6次,收藏13次。分层思想分层思想分层思想-1分层思想-2分层思想-2OSI七层参考模型物理层和数据链路层物理层数据链路层网络层传输层会话层表示层应用层OSI七层模型的分层结构TCP/IP协议族的组成数据封装过程数据解封装过程PDU设备与层的对应关系各层通信分层思想分层思想-1在现实生活种,我们在喝牛奶时,未必了解他的生产过程,我们所接触的或许只是从超时购买牛奶。分层思想-2平时我们在网络时也未必知道数据的传输过程我们的所考虑的就是可以传就可以,不用管他时怎么传输的分层思想-2将复杂的流程分解为几个功能_5g分层结构

基于二值化图像转GCode的单向扫描实现-程序员宅基地

文章浏览阅读191次。在激光雕刻中,单向扫描(Unidirectional Scanning)是一种雕刻技术,其中激光头只在一个方向上移动,而不是来回移动。这种移动方式主要应用于通过激光逐行扫描图像表面的过程。具体而言,单向扫描的过程通常包括以下步骤:横向移动(X轴): 激光头沿X轴方向移动到图像的一侧。纵向移动(Y轴): 激光头沿Y轴方向开始逐行移动,刻蚀图像表面。这一过程是单向的,即在每一行上激光头只在一个方向上移动。返回横向移动: 一旦一行完成,激光头返回到图像的一侧,准备进行下一行的刻蚀。

算法随笔:强连通分量-程序员宅基地

文章浏览阅读577次。强连通:在有向图G中,如果两个点u和v是互相可达的,即从u出发可以到达v,从v出发也可以到达u,则成u和v是强连通的。强连通分量:如果一个有向图G不是强连通图,那么可以把它分成躲个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任一点强连通,成这样的一个“极大连通”子图是G的一个强连通分量(SCC)。强连通分量的一些性质:(1)一个点必须有出度和入度,才会与其他点强连通。(2)把一个SCC从图中挖掉,不影响其他点的强连通性。_强连通分量

Django(2)|templates模板+静态资源目录static_django templates-程序员宅基地

文章浏览阅读3.9k次,点赞5次,收藏18次。在做web开发,要给用户提供一个页面,页面包括静态页面+数据,两者结合起来就是完整的可视化的页面,django的模板系统支持这种功能,首先需要写一个静态页面,然后通过python的模板语法将数据渲染上去。1.创建一个templates目录2.配置。_django templates

linux下的GPU测试软件,Ubuntu等Linux系统显卡性能测试软件 Unigine 3D-程序员宅基地

文章浏览阅读1.7k次。Ubuntu等Linux系统显卡性能测试软件 Unigine 3DUbuntu Intel显卡驱动安装,请参考:ATI和NVIDIA显卡请在软件和更新中的附加驱动中安装。 这里推荐: 运行后,F9就可评分,已测试显卡有K2000 2GB 900+分,GT330m 1GB 340+ 分,GT620 1GB 340+ 分,四代i5核显340+ 分,还有写博客的小盒子100+ 分。relaybot@re...

推荐文章

热门文章

相关标签