使用python爬取微信公众号文章

突然开始关注爬虫,是因为喜欢上了一位作者,想要把他互联网上发表的所有文章和博文都收藏起来,方便自己阅读和分析,同时避免将来哪一天因为不可抗力无法查看(最近很喜欢的一位作者被全网炸号了,所有文章都没了,好可惜),

所以本次爬虫的目标很简单:

1. 获取公众号所有文章的标题和正文

2. 获取所有文章的发表时间

3. 获取所有文章的链接

4. 获取文章的评论(可选)

5. 获取到的文章与原文一致,最好文章可编辑,文中图片正常,文内链接可跳转

6. 分析文章并重新排版(待定,目前还没准备好分析的要点)

想要实现的功能没有涉及数据分析,所以操作还是很简单的

前期准备

1. 准备一个微信个人公众号

2. 电脑已安装python

3. 代码执行时,部分模块需要安装,安装语句:pip install 包名

  • wechatsogou    抓取公众号文章库,安装这个包会把oupsieve, beautifulsoup4, Pillow, lxml, future, bs4包也安装了
  • Werkzeug    WSGI⼯具库, 如Client类,EnvironBuilder类,debugg工具,这个库默认存在的,但是1.0.1版本的werkzeug.contrib已经被移除了,所以我们要卸载原版本,并安装更高的版本

            步骤:pip3 uninstall Werkzeug
               pip3 install Werkzeug==0.11.15 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

  • pdfkit            用于Node和浏览器的PDF文档生成库
  • wkhtmltopdf  可以将html文件转化成pdf文件,需要先下载wkhtmltopdf命令行工具,根据自己的电脑型号下载,下载地址:https://wkhtmltopdf.org/downloads.html

          下载后解压并安装,记录下安装路径

一、批量获取公众号往期推送url链接

1. 获取微信公众号文章的长期链接

原因

由于我们查看的微信公众号的文章链接都是随机生成的,如果在前端想要获取往期推送的所有文章,就需要手动点开一个个复制,非常麻烦,所以我们用一个网上常规的方法,准备一个个人公众号,后台获取长期链接

获取步骤:

1)登录个人公众号,点击草稿箱》新的创作》写新图文》点击超链接

2)选择需要抓取的公众号,按F12获取一个开头为“appmsg”开头的url

3)解析该url

https://mp.weixin.qq.com/cgi-bin/appmsg?action=list_ex&begin=0&count=5&fakeid=MzIwMTIzNDMwNA==&type=9&query=&token=318406675&lang=zh_CN&f=json&ajax=1

https://mp.weixin.qq.com/cgi-bin/appmsg 请求的基础部分

?action=list_ex 常用于动态网站,实现不同的参数值而生成不同的页面或者返回不同的结果

&begin=0&count=5&fakeid=MzIwMTIzNDMwNA==&type=9&query=&token=318406675&lang=zh_CN&f=json&ajax=1 设置参数

4)设置url

请求参数

count:一次请求返回的个数

begin:当前请求的页数,当begin为0时,请求后返回最新的5篇文章

2.获取Cookie和User-Agent

只有url是无法访问的,因为我们获取url时是登录了个人公众号的,直接用python访问是未登录的,所以我们还得获取请求头。

1)新建一个yaml文件,存入我们公众号的标识符fakeid以及token参数

cookie : appmsglist_action_3920...
user_agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64)....
fakeid : MzI4M……
token : "3184……
header参数

 2)解析yaml文件

1 import yaml
2 with open("wechat.yaml", "r") as file:
3     file_data = file.read()
4 config = yaml.safe_load(file_data) 
5 
6 headers = {
7     "Cookie": config['cookie'],
8     "User-Agent": config['user_agent'] 
9 }
解析yaml

二、爬取文章

1. 爬取单个文章

接口访问需要的内容我们准备好了,接下来我们先试下单个接口抓取公众号最新文章,看下准备的数据是否正确,同时看下接口的返回值,找出我们需要的内容

操作步骤:

1)进入cmd页面,输入python进入python编译器

2)  导入requests 模块,执行gel接口,接口里的cookie和agent信息是上面步骤1.2获取的,需要自己手动填入,下图可以看出我们抓取成功了

 1 requests.get("https://mp.weixin.qq.com/cgi-bin/appmsg", headers={
 2     "Cookie": "cookie内容根据自己接口修改",
 3     "User-Agent": "根据自己接口修改" 
 4 }, params = {
 5     "action": "list_ex",
 6     "begin": "0",
 7     "count": "1",
 8     "fakeid": "根据自己接口的fakeid修改",
 9     "type": "9",
10     "token": "根据自己接口的token修改",
11     "lang": "zh_CN",
12     "f": "json",
13     "ajax": "1"
14 }, verify=False).json()
爬取单个接口

解析json返回值

下图是接口的返回信息,可以看出对我们有用的信息有:文章id,文章创建时间,文章链接,文章标题

2. 爬取所有文章

现在我们已经准备好接口了,开始抓取公众号所有文章,由于不知道公众号具体有多少文章,所以采用循环的方式抓取,直到所有文章抓取完成。同时代码还实现了跳过之前已抓取部分,但是仍有很多不合理的地方,比如在公众号发了新文后,就会有少量重复数据,以及之前抓取过的话,会导致最新文章抓不到,等等。因为这个写的比较简易,大家可以自行优化。

附上代码:

 1 import json
 2 import requests
 3 import time
 4 import random
 5 import yaml
 6 import os
 7 
 8 # headers解析
 9 with open("headers.yaml", 'rb') as fp:
10     config = yaml.load(fp, Loader=yaml.SafeLoader)
11 
12 headers = {
13     "Cookie": config['cookie'],
14     "User-Agent": config['user_agent'] 
15 }
16 
17 # 请求参数设置
18 url = "https://mp.weixin.qq.com/cgi-bin/appmsg"
19 begin = "0"         # begin 为开始页码,0代表第1页,从最新开始抓
20 count = "5"         # count 为每页获取的文章个数
21 params = {
22     "action": "list_ex",
23     "begin": begin,
24     "count": count,
25     "fakeid": config['fakeid'],
26     "type": "9",
27     "token": config['token'],
28     "lang": "zh_CN",
29     "f": "json",
30     "ajax": "1"
31 }
32 
33 # 结果文件设置
34 wechat_spider_json_file = "wechat_spider_data.json"
35 
36 # 获取当前json文件内容,计算已爬取的页数
37 if os.path.exists(wechat_spider_json_file):
38     with open(wechat_spider_json_file, "r") as file:
39         wechat_app_msg_list = json.load(file)
40         #print("之前已抓取{}页文章,将从下一页开始抓取".format(1+len(wechat_app_msg_list))
41 else:
42     wechat_app_msg_list = []
43 
44 i = len(wechat_app_msg_list)
45 print("之前已抓取{}页文章,将从下一页开始抓取".format(i)
46 
47 # 使用while循环获取, 直至抓取完成
48 while True:
49           
50     init = i  * int(count)
51     params["begin"] = str(init)
52     
53     # 随机等待几秒,避免被微信识别到
54     num = random.randint(1,10)     
55     print("等待{0}秒,准备抓取第{1}页,每页{2}篇".format(num, (i+1), count))
56     time.sleep(num)  
57     
58     # 执行抓取接口
59     resp = requests.get(url, headers=headers, params = params, verify=False)
60 
61     # 抓取失败,退出
62     if resp.json()['base_resp']['ret'] == 200013:
63         print("触发微信机制,抓取失败,当前抓取第{0}页,每页{1}篇".format((i+1), count))
64         break
65     
66     # 抓取完成,结束
67     if len(resp.json()['app_msg_list']) == 0:
68         print("已抓取完所有文章,共抓取{0}篇".format((i+1)*int(count))
69         break
70     
71     # 抓取成功,json格式保存返回的接口信息
72     wechat_app_msg_list.append(resp.json())
73     print("抓取第{0}页成功,每页{1}篇, 共抓取了{2}篇".format((i+1), count, (i+1)*int(count)))
74     
75     # 循环下一页
76     i += 1
77 
78 # json格式保存结果至文件
79 with open(wechat_spider_json_file, "w") as file:
80     file.write(json.dumps(wechat_app_msg_list, indent=2, ensure_ascii=False)) 
爬取所有文章

三、提取需要的信息

1. 提取接口信息

从前面步骤2.1可以知道,接口的返回结果中,我们有用的只有文章id,文章标题,文章创建时间,文章链接,我们可以把步骤2.2的代码优化下,将接口返回的这些数据提取出来,写入csv文件中。

附上代码:

可将如下代码插叙步骤2.2的代码中

1 # 文章的id,标题,创建时间,链接写入csv文件
2 for item in resp.json()['app_msg_list']:    
3 
4     arti_create_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(item['create_time']))
5     arti_info = '"{}","{}","{}","{}"'.format(str(item["aid"]), str(arti_create_time), item['link'], item['title'])
6     with open("app_msg_list.csv", "a", encoding='utf-8-sig') as f:
7         f.write(arti_info+'\n')
8         
9 print("第{0}页文章的id,标题,创建时间,链接写入csv文件app_msg_list.csv成功".format(i+1))
写入csv文件

 

输出结果如下图:

2. 访问接口获取详细信息

到这里,我们现在就还差正文信息了,而这才是我最想要的内容,但是使用python直接访问url的话获取到的内容并不是我们想要的文章内容,而是html文件,所以在这一步我试了几种方法去实现,一开始想的是存储文章的图片到csv中,方便查看,但是最终放弃了,使用了下方的第4种。

1)第一种:使用正则。通过正则在html中筛选出自己要的内容,这个太麻烦了,还得自己对文章排版,直接pass了

2)  第二种:使用BeautifulSoup库。 BeautifulSoup库的解析数据功能强大,可以通过直接获取文本,这个我用了后发现它提取出了很多页面的多余字符,比如点赞,收藏等,而且只有文本,要想插入其他信息还得根据标签提取,太麻烦了。写了段简单的代码测试功能,这个用来处理纯文本还是很方便的。所以该方案也被pass了

import lxml
import requests
from bs4 import BeautifulSoup

url = 'http://mp.weixin.qq.com'
res = requests.get(url)
#print(response.encoding) 
#response =(res.text).encode('ISO-8859-1').decode('UTF-8')
content = BeautifulSoup(res.text,'lxml').get_text()
text = re.sub('\s','',content)
BeautifulSoup 解析数据

 

想要学习该方法的可以看下这个链接:https://blog.csdn.net/weixin_54852327/article/details/115916146

 

3)第三种:使用selenium截图搭配PIL处理图片。selenium截取图片很方便,这种方式可以原生态的保留文章信息,缺点是无法编辑。顺便处理了下长文章截图不全问题,属于我的备选方案。

附上代码:

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from PIL import Image
 
 
def screenshot_to_pdf_and_png(link):
    ''' 参数:网址
        功能: 保存网址截图
             解决了截图不全问题
             解决了懒加载问题
             保存俩种图片格式
    '''
    path = './'
    # 1> 获取chrome参数对象
    chrome_options = Options()
    # 2> 添加无头参数r,一定要使用无头模式,不然截不了全页面,只能截到你电脑的高度
    chrome_options.add_argument('--headless')
    # 3> 为了解决一些莫名其妙的问题关闭 GPU 计算
    chrome_options.add_argument('--disable-gpu')
    # 4> 为了解决一些莫名其妙的问题浏览器不动
    chrome_options.add_argument('--no-sandbox')
    # 5> 添加驱动地址。 由于在函数内,设置参数chrome_options需要再导入
    driver = webdriver.Chrome(executable_path=r'D:\python\chromedriver.exe' ,chrome_options=chrome_options)
    # 6> 模仿手动滑动滚动条,解决懒加载问题
    try:
        driver.implicitly_wait(20)
        driver.get(link)
 
        # 模拟人滚动滚动条,处理图片懒加载问题
        js_height = "return document.body.clientHeight"
        driver.get(link)
        k = 1
        height = driver.execute_script(js_height)
        while True:
            if k * 500 < height:
                js_move = "window.scrollTo(0,{})".format(k * 500)
                print(js_move)
                driver.execute_script(js_move)
                time.sleep(0.2)
                height = driver.execute_script(js_height)
                k += 1
            else:
                break
 
        time.sleep(1)
 
        # 7>  # 直接截图截不全,调取最大网页截图
        width = driver.execute_script(
            "return Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth);")
        height = driver.execute_script(
            "return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);")
        print(width, height)
        # 将浏览器的宽高设置成刚刚获取的宽高
        driver.set_window_size(width + 100, height + 100)
        time.sleep(1)
        png_path = path + '/{}.png'.format('xx网址截图')
 
        # 截图并关掉浏览器
        driver.save_screenshot(png_path)
        driver.close()
        # png转pdf
        image1 = Image.open(png_path)
        im1 = image1.convert('RGB')
        pdf_path = png_path.replace('.png', '.pdf')
        im1.save(pdf_path)
        return pdf_path
 
    except Exception as e:
        print(e)
selenium 对文章截图

 

4)第四种:使用pdfkit。直接将url处理成pdf文档,该方法简单方便,转换的pdf文档内容与原文章排版一致,文档内容可以自行加批注,文内链接也可点击,还可以自己设置标题和正文,推荐使用。

使用该方法的时候,我发现如果直接用的话,会显示不了文章中的图片,所以优化了下代码。

附上代码:

import pdfkit
import datetime
import wechatsogou

ws_api = wechatsogou.WechatSogouAPI(captcha_break_time=3) 

def url2pdf(url, title):
    '''
    使用pdfkit生成pdf文件
    :param url: 文章url
    :param title: 文章标题
    :param targetPath: 存储pdf文件的路径
    '''
    
    try:
        content_info = ws_api.get_article_content(url)
        path_wkthmltopdf = r'D:\wkhtmltox\wkhtmltopdf\bin\wkhtmltopdf.exe'    
        config = pdfkit.configuration(wkhtmltopdf=path_wkthmltopdf) 
        
    except:
        return False
        
    # 处理后的html
    html = f'''
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{title}</title>
    </head>
    <body>
    <h2 style="text-align: center;font-weight: 400;">{title}</h2>
    {content_info['content_html']}
    </body>
    </html>
    '''
    
    pdfkit.from_string(html, 'test.pdf', configuration=config)
url2pdf 方法

 

到了这里,我们整个流程就结束了,还剩下一些优化,比如pdf合并,pdf文档排版优化,日志打印,以及代码模块化等等,大家可以自行优化。

 

因为之前很少爬虫,所以整个流程的处理都是根据自己需要+百度出来的,如果有更好的方法,希望好心人能留言让我知道,万分感谢。

posted on 2022-05-19 20:06  公元12956  阅读(8949)  评论(0编辑  收藏  举报

导航