使用Python爬取公号文章(上)

第一时间获取 Python 技术干货!

阅读文本大概需要 10 分钟。

01
抓取目标

场景:有时候我们想爬取某个大 V 的发布的全部的文章进行学习或者分析。

这个爬虫任务我们需要借助「 Charles 」这个抓包工具,设置好手机代理 IP 去请求某个页面,通过分析,模拟请求,获取到实际的数据。

我们要爬取文章的作者、文章标题、封面图、推送时间、文件内容、阅读量、点赞数、评论数、文章实际链接等数据,最后要把数据存储到「 MongoDB 」数据库中。

02
准备工作

首先,在 PC 上下载 Charles,并获取本地的 IP 地址。

然后,手机连上同一个网段,并手动设置代理 IP,端口号默认填 8888 。最后配置 PC 和手机上的证书及 SSL Proxying,保证能顺利地抓到 HTTPS 的请求。具体的方法可以参考下面的文章。

「https://www.jianshu.com/p/595e8b556a60?from=timeline&isappinstalled=0 」

03
爬取思路

首先我们选中一个微信公众号,依次点击右上角的头像、历史消息,就可以进入到全部消息的主界面。默认展示的是前 10 天历史消息。

然后可以查看 Charles 抓取的请求数据,可以通过「 mp.weixin.qq.com 」去过滤请求,获取到消息首页发送的请求及请求方式及响应内容。

继续往下滚动页面,可以加载到下一页的数据,同样可以获取到请求和响应的数据。

爬取的数据最后要保存在 MongoDB 文档型数据库中,所以不需要建立数据模型,只需要安装软件和开启服务就可以了。MongoDB 的使用教程可以参考下面的链接:

「 https://www.jianshu.com/p/4c5deb1b7e7c 」

为了操作 MongoDB 数据库,这里使用「 MongoEngine 」这个类似于关系型数据库中的 ORM 框架来方便我们处理数据。

pip3 install mongoengine
04
代码实现

从上面的分析中可以知道首页消息、更多页面消息的请求 URL 规律如下:

# 由于微信屏蔽的关键字, 字段 netloc + path 用 ** 代替
# 首页请求url
https://**?action=home&__biz=MzIxNzYxMTU0OQ==&scene=126&bizpsid=0&sessionid=1545633855&subscene=0&devicetype=iOS12.1.2&version=17000027&lang=zh_CN&nettype=WIFI&a8scene=0&fontScale=100&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wx_header=1

# 第二页请求url
https://**?action=getmsg&__biz=MzIxNzYxMTU0OQ==&f=json&offset=10&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wxtoken=&appmsg_token=988_rETfljlGIZqE%252F6MobN1rEtqBx5Ai9wBDbbH_sw~~&x5=0&f=json

# 第三页请求url
https://**?action=getmsg&__biz=MzIxNzYxMTU0OQ==&f=json&offset=21&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wxtoken=&appmsg_token=988_rETfljlGIZqE%252F6MobN1rEtqBx5Ai9wBDbbH_sw~~&x5=0&f=json

可以通过把 offset 设置为可变数据,请求所有页面的数据 URL可以写成下面的方式:

https://**?action=getmsg&__biz=MzIxNzYxMTU0OQ==&f=json&offset={}&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wxtoken=&appmsg_token=988_rETfljlGIZqE%252F6MobN1rEtqBx5Ai9wBDbbH_sw~~&x5=0&f=json

另外,通过 Charles 获取到请求头。由于微信的反爬机制,这里的 Cookie 和 Referer 有一定的时效性,需要定时更换。

self.headers = {
            'Host': 'mp.weixin.qq.com',
            'Connection': 'keep-alive',
            'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16B92 MicroMessenger/6.7.4(0x1607042c) NetType/WIFI Language/zh_CN',
            'Accept-Language': 'zh-cn',
            'X-Requested-With': 'XMLHttpRequest',
            'Cookie': 'devicetype=iOS12.1; lang=zh_CN; pass_ticket=fXbGiNdtFY050x9wsyhMnmaSyaGbSIXNzubjPBqiD+c8P/2GyKpUSimrtIKQJsQt; version=16070430; wap_sid2=CMOw8aYBElx2TWQtOGJfNkp3dmZHb3dyRnpRajZsVlVGX0pQem4ycWZSNzNFRmY3Vk9zaXZUM0Y5b0ZpbThVeWgzWER6Z0RBbmxqVGFiQ01ndFJyN01LNU9PREs3OXNEQUFBfjC409ngBTgNQJVO; wxuin=349984835; wxtokenkey=777; rewardsn=; pac_uid=0_f82bd5abff9aa; pgv_pvid=2237276040; tvfe_boss_uuid=05faefd1e90836f4',
            'Accept': '*/*',
            'Referer': 'https://**?action=home&__biz=MzIxNzYxMTU0OQ==&scene=126&sessionid=1544890100&subscene=0&devicetype=iOS12.1&version=16070430&lang=zh_CN&nettype=WIFI&a8scene=0&fontScale=100&pass_ticket=pg%2B0C5hdqENXGO6Fq1rED9Ypx20C2vuodaL8DCwZwVe22sv9OtWgeL5YLjUujPOR&wx_header=1'
        }

最后通过 requests 去模拟发送请求。

response = requests.get(current_request_url, headers=self.headers, verify=False)
result = response.json()

通过 Charles 返回的数据格式可以得知消息列表的数据存储在 general_msg_list 这个 Key 下面。因此可以需要拿到数据后进行解析操作。

has_next_page 字段可以判断是否存在下一页的数据;如果有下一页的数据,可以继续爬取,否则终止爬虫程序。

ps:由于 Wx 反爬做的很完善,所以尽量降低爬取的速度。

response = requests.get(current_request_url, headers=self.headers, verify=False)
        result = response.json()

if result.get("ret") == 0:
            msg_list = result.get('general_msg_list')

# 保存数据
            self._save(msg_list)
            self.logger.info("获取到一页数据成功, data=%s" % (msg_list))

# 获取下一页数据
            has_next_page = result.get('can_msg_continue')
            if has_next_page == 1:
                # 继续爬取写一页的数据【通过next_offset】
                next_offset = result.get('next_offset')

# 休眠2秒,继续爬下一页
                time.sleep(2)
                self.spider_more(next_offset)
            else:  # 当 has_next 为 0 时,说明已经到了最后一页,这时才算爬完了一个公众号的所有历史文章
                print('爬取公号完成!')
        else:
            self.logger.info('无法获取到更多内容,请更新cookie或其他请求头信息')

由于获取到的列表数据是一个字符串,需要通过 json 库去解析,获取有用的数据。
def _save(self, msg_list):
        """
        数据解析
        :param msg_list:
        :return:
        """
        # 1.去掉多余的斜线,使【链接地址】可用
        msg_list = msg_list.replace("\/", "/")
        data = json.loads(msg_list)

# 2.获取列表数据
        msg_list = data.get("list")
        for msg in msg_list:
            # 3.发布时间
            p_date = msg.get('comm_msg_info').get('datetime')

# 注意:非图文消息没有此字段
            msg_info = msg.get("app_msg_ext_info")

if msg_info:  # 图文消息
                # 如果是多图文推送,把第二条第三条也保存
                multi_msg_info = msg_info.get("multi_app_msg_item_list")

# 如果是多图文,就从multi_msg_info中获取数据插入;反之直接从app_msg_ext_info中插入
                if multi_msg_info:
                    for multi_msg_item in multi_msg_info:
                        self._insert(multi_msg_item, p_date)
                else:
                    self._insert(msg_info, p_date)
            else:
                # 非图文消息
                # 转换为字符串再打印出来
                self.logger.warning(u"此消息不是图文推送,data=%s" % json.dumps(msg.get("comm_msg_info")))

最后一步是将数据保存保存到 MongoDB 数据库中。
首先要创建一个 Model 保存我们需要的数据。
from datetime import datetime

from mongoengine import connect
from mongoengine import DateTimeField
from mongoengine import Document
from mongoengine import IntField
from mongoengine import StringField
from mongoengine import URLField

__author__ = 'xag'

# 权限连接数据库【数据库设置了权限,这里必须指定用户名和密码】
response = connect('admin', host='localhost', port=27017,username='root', password='xag')

class Post(Document):
    """
    文章【模型】
    """
    title = StringField()  # 标题
    content_url = StringField()  # 文章链接
    source_url = StringField()  # 原文链接
    digest = StringField()  # 文章摘要
    cover = URLField(validation=None)  # 封面图
    p_date = DateTimeField()  # 推送时间
    author = StringField()  # 作者

content = StringField()  # 文章内容

read_num = IntField(default=0)  # 阅读量
    like_num = IntField(default=0)  # 点赞数
    comment_num = IntField(default=0)  # 评论数
    reward_num = IntField(default=0)  # 点赞数

c_date = DateTimeField(default=datetime.now)  # 数据生成时间
    u_date = DateTimeField(default=datetime.now)  # 数据最后更新时间

使用命令行开启数据库服务,然后就可以往数据库写入数据了。
  def _insert(self, msg_info, p_date):
        """
        数据插入到 MongoDB 数据库中
        :param msg_info:
        :param p_date:
        :return:
        """
        keys = ['title', 'author', 'content_url', 'digest', 'cover', 'source_url']

# 获取有用的数据,构建数据模型
        data = sub_dict(msg_info, keys)
        post = Post(**data)

# 时间格式化
        date_pretty = datetime.fromtimestamp(p_date)
        post["p_date"] = date_pretty

self.logger.info('save data %s ' % post.title)

# 保存数据
        try:
            post.save()
        except Exception as e:
            self.logger.error("保存失败 data=%s" % post.to_json(), exc_info=True)

05
爬取结果

推荐使用工具 Robo3T 连接 MongoDB 数据库,可以查看到公号文章数据已经全部保存到数据库中。

(0)

相关推荐

  • Flask开发技巧之参数校验

    目录 Flask开发技巧之参数校验 1.请求参数分类 2.解决方案使用到的库 3.针对url查询参数与一般json格式 4.针对复杂json格式数据 本人平时开发中使用的,或者学习到的一些flask开 ...

  • python测试开发django-118.json 解析查询数据库 datetime 格式问题

    前言 django 查询的结果有日期时间格式的时候,返回的是datetime.datetime(2021, 9, 8, 0, 0)类型数据. 一般需要返回json格式数据,使用json.dumps() ...

  • TelloPy-develop-0.7.0源码阅读.1

    最近我在反思,为什么我看了那么多书,为什么还是写不出大型的程序?我也很苦恼,我想了下.应该还是看的源码少的过,古人曾经说过熟读唐诗三百首,不会吟诗也会吟 .在读源码的选择上,我没有选择太复杂的开源库, ...

  • 这有 73 个例子,彻底掌握 f-string 用法!

    英文:Miguel Brito,翻译:Python开发者 /  felixGuo26 在本文中,我将向你展示我认为对 Python 格式化字符串 f-string 来说最重要的一些技巧.你会通过各种样 ...

  • 用 Python 抓取公号文章保存成 PDF

    今天为大家介绍如何将自己喜欢的公众号的历史文章转成 PDF 保存到本地.前几天还有朋友再问,能不能帮把某某公众号的文章下载下来,因为他很喜欢这个号的文章,但由于微信上查看历史文章不能排序,一些较早期的 ...

  • 用 Python 抓取公号文章保存成 HTML

    上次为大家介绍了如果用 Python 抓取公号文章并保存成 PDF 文件存储到本地.但用这种方式下载的 PDF 只有文字没有图片,所以只适用于没有图片或图片不重要的公众号,那如果我想要图片和文字下载下 ...

  • python爬取你喜欢的公众号的所有原创文章,然后搞成PDF慢慢看

    我知道你有时候会遇到了 一个相见恨晚的公众号 比如小帅b的公众号哈哈 然后想去看看 这个公众号的历史文章 希望从第一篇开始看起 可是当你去微信里面查看的时候 会很蛋疼 因为 微信没有给文章一个时间排序 ...

  • Python爬取房产数据,在地图上展现!

    小伙伴,我又来了,这次我们写的是用python爬虫爬取乌鲁木齐的房产数据并展示在地图上,地图工具我用的是 BDP个人版-免费在线数据分析软件,数据可视化软件 ,这个可以导入csv或者excel数据. ...

  • 随着身子的一阵颤抖,Python爬取抖音上的小姐姐突然变得索然无味

    在抖音刚出的那会 小帅b就被抖音上 的小姐姐迷得不行 毕竟在现实生活中 小帅b比较宅 很少遇到一些这样的小姐姐 除非小姐姐自己上门 (开玩笑开玩笑) 她们 说话好听 貌美如花 人又善良 皮肤光滑 身材 ...

  • Python爬取CSDN文章,并制作成PDF文档

    Python爬取CSDN文章,并制作成PDF文档

  • 使用 Python 爬取简书网的所有文章

    第一时间获取 Python 技术干货! 阅读文本大概需要 6 分钟. 01 抓取目标 我们要爬取的目标是「 简书网 」. 打开简书网的首页,随手点击一篇文章进入到详情页面. 我们要爬取的数据有:作者. ...

  • 用Python爬取东方财富网上市公司财务报表

    摘要: 现在很多网页都采取JavaScript进行动态渲染,其中包括Ajax技术.有的网页虽然也用Ajax技术,但接口参数可能是加密的无法直接获得,比如淘宝:有的动态网页也采用JavaScript,但 ...

  • Python爬取某平台短视频,把你喜欢的视频下收藏起来

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 基本开发环境 Python 3.6 Pycharm 相关模块的使用 import osimp ...