技术栈:抓包技术、Python2.7、BeautifulSoup、Requests、PyMuPDF、一些HTTP协议基础。
0x00、问题提出
想要对某公众号的历史推送的图片进行归档,但是微信没有相关的功能,历史推送数据量还不小,因此决定做一个爬虫。
做这个爬虫的思路是,首先得到历史推送的url集合,然后依次爬取,解析我们想要的数据。
注意:这里我的需求是得到所有的图片,所以文字可以忽略掉,我只需要爬取并下载图片,插入到PDF即可。
0x01、抓包分析,批量获取URL
公众号有历史推送列表的h5页面:
但是在电脑是打不开这个页面的,因为微信把流量封在app里面了,需要在发送http请求时加入特定的header,从外部打开是不可以的。
其实微信用的浏览器是修改过内核的,里面加入了很多微信的sdk。
这时候用到了这个工具:
微信公众平台的官网就能换下载到,找不到就百度以下,肯定能下载到。
接着打开它,进行移动调试:
根据说明,在手机上设置代理:
这一步的作用是让从路由器的流量全部通过电脑,再到手机,让电脑监听手机的网络。
然后我们找到微信公众号的这个界面:
向下来会滑动,加载的多一些,然后看电脑上的抓包结果:
可以看到,已经抓到包了,由于微信通过HTTPS进行传输,我们看不到加密后的响应内容,但是由于我们刚刚往下滑的时候加载了几页,所以找API相同,但是参数不同的请求,就有很大的可能是获取历史消息的API。
注意到这几个包,只有offset不同,offset是偏移量,可以认为,这个包就是获取历史消息的API。
为了研究出微信防盗链原理,随便打开一个包,看看header:
有好多微信客户端专用的header字段,我们把他们丢到一个字典中,让爬虫直接抓取API的时候发送这些header。
我们使用Curl工具带Header请求这个API,发现返回来的结果是Json类型:
分析其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,得到的结果是这样的:
我们的图片存放在了一个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
最后: