之前虽然做过网页爬取,但微信爬取一直没做过,因为我一直不知道网页可以进微信公众平台,只用过微信客户端进微信公众号。既然可以通过网页进微信公众平台,那么爬取微信公众号文章就流程上就没太多难度了。
自己在网上找的一些python爬虫文章不太好用,就亲自写了一套,包括详细的页面附图和代码注释,代码复制下来后可以直接运行,供需要的同行参考交流。 爬取微信公众号文章之前,需要先申请微信公众号,针对个人使用的订阅号就行。
爬取微信公众号文章有两种思路:
一种是通过搜狗浏览器爬取,因为微信公众平台为搜狗提供了访问接口。
第二种种就是通过google或firefox浏览器来爬取,本篇文章只讲解第二种方式,但第一种方式也可以借鉴第二种方式的部分代码。
1、通过调用谷歌或火狐浏览器驱动,模拟微信公众号登录,获取到cookies(里面包含登录身份的token值),将cookies保存到本地文件中,供以后面访问微信公众号时携带身份识别之用。
页面如下:
点击登录后跳出来扫描验证页面:
login.py文件代码如下:
# -!- coding: utf-8 -!-
from selenium import webdriver
import time
import json
#谷歌和火狐两种驱动人选一种即可
# 调用谷歌浏览器驱动 如果本地电脑未安装谷歌驱动,请网上下载
driver = webdriver.Chrome()
# 调用火狐浏览器驱动 如果本地电脑未安装火狐驱动,请网上下载
# driver = webdriver.Firefox()
driver.get("https://mp.weixin.qq.com/") # 微信公众平台网址
driver.find_element_by_link_text("使用帐号登录").click() # 此行代码为2020.10.10新增,因为微信公众号页码原来默认直接为登录框, 现在默认为二维码,此行代码可以将二维码切换到登录框页面
driver.find_element_by_name("account").clear()
driver.find_element_by_name("account").send_keys("1466617803@qq.com") # 自己的微信公众号
time.sleep(2)
driver.find_element_by_name("password").clear()
driver.find_element_by_name("password").send_keys("*******") # 自己的微信公众号密码
driver.find_element_by_class_name("icon_checkbox").click()
time.sleep(2)
driver.find_element_by_class_name("btn_login").click()
time.sleep(15)
#此时会弹出扫码页面,需要微信扫码
cookies = driver.get_cookies() # 获取登录后的cookies
print(cookies)
cookie = {}
for items in cookies:
cookie[items.get("name")] = items.get("value")
# 将cookies写入到本地文件,供以后程序访问公众号时携带作为身份识别用
with open('cookies.txt', "w") as file:
# 写入转成字符串的字典
file.write(json.dumps(cookie))
2、扫码后进入公众号首页,点击左侧的“图文素材”,在点击右侧的“图文模板”,如下图:
3、点击完“图文模板”后进入如下界面,并点击“新建图文模板”:
4、点击完“新建图文模板”后,等待5~6秒,进入如下页面:
5、点击上图中红色框处的“超链接”,进入如下页面,点击“选择其他公众号”,选择查找文章,在公众号中输入要查询的公众号,并点击输入框右侧的放大镜查询标识,例如输入“共轨之家”(别想歪了哈,只是个介绍汽车维修资料的公众号哈):
6、选中搜索出来的符合要求的公众号(当然也可以用微信公众号的唯一id搜,例如:gongguizhijia),单击后进入公众号,可以看到该公众号下的文章,如下图:
下拉到底部,可以看到页码:
7、此时要上代码了,先分析一下文章的分页和F12后可以看到的请求信息。
(1)、我们看到总共53页,每页展示20条。
(2)、F12查看请求方式,请求头如下图:
(3)不断的点击不同页码,发现查询参数begin的值和页码的关系为:begin=(当前页-1)*5,count值始终为5,token值可以从浏览器中获取。
8、爬取该公众号下的所有文章的名字、唯一表示、链接,保存到本地文件article_link中,供爬取文章页面用(此处微信有访问频率限制,具体多少没详细测,我连续访问32次后被限制访问,提醒我操作频繁)。
get_article_link.py代码如下:
# -*- coding:utf-8 -*-
import requests
import json
import re
import random
import time
with open("cookies.txt", "r") as file:
cookie = file.read()
cookies = json.loads(cookie)
url = "https://mp.weixin.qq.com"
response = requests.get(url, cookies=cookies)
token = re.findall(r'token=(\d+)', str(response.url))[0] # 从url中获取token
print(token)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
"Referer": "https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=10&token="+token+"&lang=zh_CN",
"Host": "mp.weixin.qq.com",
}
# requestUrl = "https://mp.weixin.qq.com/cgi-bin/searchbiz"
with open('article_link.txt', "w", encoding='utf-8') as file:
for j in range(1, 53, 1):
begin = (j-1)*5
requestUrl = "https://mp.weixin.qq.com/cgi-bin/appmsg?token="+token+"&lang=zh_CN&f=json&ajax=1&random="+str(random.random())\
+"&action=list_ex&begin="+str(begin)+"&count=5&query=&fakeid=MzA3NDM2ODIzMQ%3D%3D&type=9"
search_response = requests.get(requestUrl, cookies=cookies, headers=headers)
re_text = search_response.json()
print(re_text)
list = re_text.get("app_msg_list")
for i in list:
file.write(i["aid"]+"<=====>"+i["title"]+"<=====>"+i["link"] + "\n")
print(i["aid"]+"<=====>"+i["title"]+"<=====>"+i["link"])
time.sleep(20)
9、获取到的链接保存格式为:
10、解析上一步获取到的文章链接,
download_article.py代码如下:
# -*- coding:utf-8 -*-
import json
import re
import time
from bs4 import BeautifulSoup
import requests
import os
# 保存页面到本地
def save_html(url_content,htmlDir,file_name):
f = open(htmlDir+"\\"+file_name+'.html', 'wb')
f.write(url_content.content) # save to page.html
f.close()
return url_content
# 修改文件,将图片路径改为本地的路径
def update_file(old, new,htmlDir):
with open(htmlDir+"\\"+file_name+'.html', encoding='utf-8') as f, open(htmlDir+"\\"+file_name+'_bak.html', 'w',
encoding='utf-8') as fw: # 打开两个文件,原始文件用来读,另一个文件将修改的内容写入
for line in f: # 遍历每行,取出来的是字符串,因此可以用replace 方法替换
new_line = line.replace(old, new) # 逐行替换
new_line = new_line.replace("data-src", "src")
fw.write(new_line) # 写入新文件
os.remove(htmlDir+"\\"+file_name+'.html') # 删除原始文件
time.sleep(10)
os.rename(htmlDir+"\\"+file_name+'_bak.html', htmlDir+"\\"+file_name+'.html') # 修改新文件名, old -> new
print('当前保存文件为:'+file_name+'.html')
# 保存图片到本地
def save_file_to_local(htmlDir,targetDir,search_response,domain):
obj = BeautifulSoup(save_html(search_response,htmlDir,file_name).content, 'lxml') # 后面是指定使用lxml解析,lxml解析速度比较快,容错高。
imgs = obj.find_all('img')
# 将页面上图片的链接加入list
urls = []
for img in imgs:
if 'data-src' in str(img):
urls.append(img['data-src'])
elif 'src=""' in str(img):
pass
elif "src" not in str(img):
pass
else:
urls.append(img['src'])
# 遍历所有图片链接,将图片保存到本地指定文件夹,图片名字用0,1,2...
i = 0
for each_url in urls: # 看下文章的图片有哪些格式,一一处理
if each_url.startswith('//'):
new_url = 'https:' + each_url
r_pic = requests.get(new_url)
elif each_url.startswith('/') and each_url.endswith('gif'):
new_url = domain + each_url
r_pic = requests.get(new_url)
elif each_url.endswith('png') or each_url.endswith('jpg') or each_url.endswith('gif') or each_url.endswith('jpeg'):
r_pic = requests.get(each_url)
t = os.path.join(targetDir, str(i) + '.jpeg') # 指定目录
print('当前保存图片为:' + t)
fw = open(t, 'wb') # 指定绝对路径
fw.write(r_pic.content) # 保存图片到本地指定目录
i += 1
update_file(each_url, t, htmlDir) # 将老的链接(有可能是相对链接)修改为本地的链接,这样本地打开整个html就能访问图片
fw.close()
#下载html页面和图片
def save(search_response,file_name):
htmlDir = os.path.join(os.path.dirname(os.path.abspath(__file__)), file_name)
targetDir = os.path.join(os.path.dirname(os.path.abspath(__file__)),file_name+'\imgs1') # 图片保存的路径,eg,向前文件夹为'D:\Coding', 即图片保存在'D:\Coding\imgs1\'
if not os.path.isdir(targetDir): # 不存在创建路径
os.makedirs(targetDir)
domain = 'https://mp.weixin.qq.com/s'
save_html(search_response, htmlDir,file_name)
save_file_to_local(htmlDir, targetDir, search_response, domain)
# 获得登录所需cookies
with open("cookies.txt", "r") as file:
cookie = file.read()
cookies = json.loads(cookie)
url = "https://mp.weixin.qq.com"
response = requests.get(url, cookies=cookies)
token = re.findall(r'token=(\d+)', str(response.url))[0]
print(token)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
"Referer": "https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=10&token="+token+"&lang=zh_CN",
"Host": "mp.weixin.qq.com",
}
f = open("article_link.txt", encoding='utf-8') # 返回一个文件对象
line = f.readline() # 调用文件的 readline()方法
for line in open("article_link.txt", encoding='UTF-8'):
new_line = line.strip()
line_list = new_line.split("<=====>")
file_name = line_list[0]
dir_name = line_list[1]
requestUrl = line_list[2]
search_response = requests.get(requestUrl, cookies=cookies, headers=headers)
save(search_response, file_name)
print(file_name+"----------------下载完毕:"+dir_name+"----------------下载完毕:"+requestUrl)
time.sleep(2)
file.close()
11、下载后文章效果:
12、选择了google、百度、火狐、ie、Edge五种浏览器测试效果,其中google、百度、Edge浏览器正常打开带图片的页面,firefox浏览器只有文字,没有图片,ie打不开页面。综上,该爬取效果基本能满足需要。