所以本次爬虫的目标很简单:
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……
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 }
二、爬取文章
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))
输出结果如下图:
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)
想要学习该方法的可以看下这个链接: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)
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)
到了这里,我们整个流程就结束了,还剩下一些优化,比如pdf合并,pdf文档排版优化,日志打印,以及代码模块化等等,大家可以自行优化。
因为之前很少爬虫,所以整个流程的处理都是根据自己需要+百度出来的,如果有更好的方法,希望好心人能留言让我知道,万分感谢。