记一次微信爬虫获取公众号历史文章并导出PDF

发布于 / Python / Comments Off on 记一次微信爬虫获取公众号历史文章并导出PDF

技术栈:抓包技术、Python2.7、BeautifulSoup、Requests、PyMuPDF、一些HTTP协议基础。

0x00、问题提出

想要对某公众号的历史推送的图片进行归档,但是微信没有相关的功能,历史推送数据量还不小,因此决定做一个爬虫。

做这个爬虫的思路是,首先得到历史推送的url集合,然后依次爬取,解析我们想要的数据。

注意:这里我的需求是得到所有的图片,所以文字可以忽略掉,我只需要爬取并下载图片,插入到PDF即可。

0x01、抓包分析,批量获取URL

公众号有历史推送列表的h5页面:

blob.png

但是在电脑是打不开这个页面的,因为微信把流量封在app里面了,需要在发送http请求时加入特定的header,从外部打开是不可以的。

其实微信用的浏览器是修改过内核的,里面加入了很多微信的sdk。


这时候用到了这个工具:

blob.png

微信公众平台的官网就能换下载到,找不到就百度以下,肯定能下载到。

接着打开它,进行移动调试:

blob.png

根据说明,在手机上设置代理:

blob.png

这一步的作用是让从路由器的流量全部通过电脑,再到手机,让电脑监听手机的网络。

然后我们找到微信公众号的这个界面:

blob.png

向下来会滑动,加载的多一些,然后看电脑上的抓包结果:

blob.png

可以看到,已经抓到包了,由于微信通过HTTPS进行传输,我们看不到加密后的响应内容,但是由于我们刚刚往下滑的时候加载了几页,所以找API相同,但是参数不同的请求,就有很大的可能是获取历史消息的API。

blob.png

注意到这几个包,只有offset不同,offset是偏移量,可以认为,这个包就是获取历史消息的API。

为了研究出微信防盗链原理,随便打开一个包,看看header:

blob.png

有好多微信客户端专用的header字段,我们把他们丢到一个字典中,让爬虫直接抓取API的时候发送这些header。

我们使用Curl工具带Header请求这个API,发现返回来的结果是Json类型:

blob.png

分析其json,发现了历史推送的Url在general_msg_list字段的list字段下,list是一个数组,每个单元是一个json,每个json中的app_msg_ext_info字段里面的title字段存放标题,content_url存放链接。

好了,我们只要以步长为10(因为微信app请求的时候每次返回10个结果),给Offset不同的参数,然后解析json,找到content_url字段就行了。

import requests as r
import json
import urllib
import fitz
import os
from bs4 import BeautifulSoup as bs

header = {
'x-wechat-uin' : '你的uin',
'upgrade-insecure-requests' : '1',
'x-wechat-key' : '你自己的key(都在抓包的界面)',
'user-agent' : 'Mozilla/5.0 (Linux; Android 8.0; MI 5 Build/OPR1.170623.032; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.143 Crosswalk/24.53.595.0 XWEB/359 MMWEBSDK/180803 Mobile Safari/537.36 MMWEBID/788 MicroMessenger/6.7.3.1360(0x26070338) NetType/WIFI Language/zh_CN Process/toolsmp',
'accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/wxpic,*/*;q=0.8',
'accept-encoding' : 'gzip, deflate',
'accept-language' : 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4',
'cookie' : '你的cookie'
}
for i in range(1, 172, 10):
  url = "https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=MzU3NTMwODMzOA==&f=json&offset=" + str(i) + "&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=cJUsojX%2FPY6rkdX9%2FfclW5Z8q879o0EaIJJlBq9ELgaQVvHVrEoU9KcDL%2F7oaMR2&wxtoken=&appmsg_token=982_2jSY7ptHIdakCD67scex1JeZHK185XSYiYftRg~~&x5=0&f=json"
  a = r.get(url, headers = header).text
  arr = json.loads(json.loads(a)['general_msg_list'])['list']
  for item in arr:
    try:
      item_url = item['app_msg_ext_info']['content_url']
      print item_url

这里为什么是172呢?因为我用二分法试了啊!只有172页~~

运行它,你会得到整个公众号所有的历史推送URL。

0x02、解析HTML,下载图片

随便访问刚刚获取的URL,得到的结果是这样的:

blob.png

我们的图片存放在了一个id为js_content的div内的img字段中,图像源在data-src属性中,用beautifulsoup解析这个div,然后用find_all选中所有的img标签,读取data-src属性:

soup = bs(item_content)
item_title = soup.title.text.replace('\n', '')
item_img_arr = soup.find('div', id='js_content').find_all('img')
print item_title
for item_img_ in item_img_arr[]:
  file_download_url = item_img_['data-src']
  print file_download_url

这样,所有的图像URL就被打印出来了。

我们定义个下载函数:

def file_download(url = '', save = ''):
  f = urllib.urlopen(url) 
  data = f.read()
  with open(save, "wb") as code:     
    code.write(data)

调用它,ojbk,图像就可以下载了。

但是!我们要的是pdf,现在得到的是图片。。

好了,接着想办法把图片插入到pdf中,每篇文章生成一个pdf。

0x03、生成pdf

这里我们用到的工具是PyMuPDF,使用pip就可以下载到。

用法如下:

doc = fitz.open()

可以新建一个pdf的对象。

fitz.open(file_name)

打开图片

pdfdata = fitz.open(file_name).convertToPDF()

打开图片,并转换成pdf格式

img = fitz.open("pdf", pdfdata)
doc.insertPDF(img)

将图像插入到pdf中

doc.save(PDF_FILE_NAME)
doc.close()

保存pdf,并关闭

好了,根据上面的东西,写出来图片插入到pdf,并自动删除下载的图片的程序:

doc = fitz.open()
file_name = '1.jpg'
for item_img_ in item_img_arr[:-1]:
  if os.path.exists(file_name):
    os.remove(file_name)
  file_download_url = item_img_['data-src']
  print file_download_url
  file_download(file_download_url, file_name)
  imgdoc = fitz.open(file_name)
  pdfbytes = imgdoc.convertToPDF()
  imgpdf = fitz.open("pdf", pdfbytes)
  doc.insertPDF(imgpdf)
doc.save(item_title + '.pdf')
doc.close()

把整个程序总结一遍,就是:

import requests as r
import json
import urllib
import fitz
import os
from bs4 import BeautifulSoup as bs

header = {
'x-wechat-uin' : '你的uin',
'upgrade-insecure-requests' : '1',
'x-wechat-key' : '你自己的key(都在抓包的界面)',
'user-agent' : 'Mozilla/5.0 (Linux; Android 8.0; MI 5 Build/OPR1.170623.032; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.143 Crosswalk/24.53.595.0 XWEB/359 MMWEBSDK/180803 Mobile Safari/537.36 MMWEBID/788 MicroMessenger/6.7.3.1360(0x26070338) NetType/WIFI Language/zh_CN Process/toolsmp',
'accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/wxpic,*/*;q=0.8',
'accept-encoding' : 'gzip, deflate',
'accept-language' : 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4',
'cookie' : '你的cookie'
}

def file_download(url = '', save = ''):
  f = urllib.urlopen(url) 
  data = f.read()
  with open(save, "wb") as code:     
    code.write(data)

for i in range(1, 172, 10):
  url = "https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=MzU3NTMwODMzOA==&f=json&offset=" + str(i) + "&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=cJUsojX%2FPY6rkdX9%2FfclW5Z8q879o0EaIJJlBq9ELgaQVvHVrEoU9KcDL%2F7oaMR2&wxtoken=&appmsg_token=982_2jSY7ptHIdakCD67scex1JeZHK185XSYiYftRg~~&x5=0&f=json"
  a = r.get(url, headers = header).text
  arr = json.loads(json.loads(a)['general_msg_list'])['list']
  for item in arr:
    try:
      item_url = item['app_msg_ext_info']['content_url']
      item_content = r.get(item_url, headers = header).text
      soup = bs(item_content)
      item_title = soup.title.text.replace('\n', '')
      item_img_arr = soup.find('div', id='js_content').find_all('img')
      print item_title
      doc = fitz.open()
      file_name = '1.jpg'
      for item_img_ in item_img_arr[:-1]:
        if os.path.exists(file_name):
          os.remove(file_name)
        file_download_url = item_img_['data-src']
        print file_download_url
        file_download(file_download_url, file_name)
        imgdoc = fitz.open(file_name)
        pdfbytes = imgdoc.convertToPDF()
        imgpdf = fitz.open("pdf", pdfbytes)
        doc.insertPDF(imgpdf)
      doc.save(item_title + '.pdf')
      doc.close()
    except:
      continue

最后:

blob.png

转载原创文章请注明,转载自: 斐斐のBlog » 记一次微信爬虫获取公众号历史文章并导出PDF
评论已关闭