• 使用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文档排版优化,日志打印,以及代码模块化等等,大家可以自行优化。

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

  • 相关阅读:
    eclipse集成testng插件(离线安装方式+ 在线安装方式)
    javaw.exe in your current path的解决方法
    Java单元测试工具:JUnit4(四)——JUnit测试套件使用及参数化设置
    Java单元测试工具:JUnit4(三)——JUnit详解之运行流程及常用注解
    Java单元测试工具:JUnit4(二)——JUnit使用详解
    Java单元测试工具:JUnit4(一)——概述及简单例子
    MINA框架使用
    UDP/IP + NIO实现系统间通信
    UDP/IP+BIO/NIO/多播
    java分布式开发TCP/IP NIO无阻塞 Socket((基于消息方式实现系统间的通信) )
  • 原文地址:https://www.cnblogs.com/leslie12956/p/16282238.html
Copyright © 2020-2023  润新知