最终解决方案
通过搜狗微信先检索公众号,获取公众号主页链接,接着爬每一篇具体文章,具体用selenium实现,当然你也可以用webkit、Geoko渲染引擎自己去渲染。用selenium、webkit、Geoko能省去分析网页Js Ajax部分加载逻辑。关于selenium的一些常用操作,后续抽个时间单独写一篇博文儿~。
一般公司内部会部署自己爬虫平台,通过代理池能最终解决此问题,当然也可以用免费开源项目IPProxyPool利用免费爬到的IP构建代理池。在没有用代理池遇到验证码反爬措施时,我们自动识别并填写并维护一个cookie池,即能降低验证码出现的频率。
遇到验证码时的理想状态是自动识别,可通过购买现成接口或自己用CNN训练个模型。本文用的是人肉识别手动填写…
难点
- 通过公众号检索页面检索时,超过一定次数后就会出现验证码。
- 公众号主页的链接URL会失效。
- 通过搜狗微信平台检索出的结果页面HTML数据中,匹配到的公众主页URL不能直接而访问,直接访问会被反爬措施限制,需要经过js处理生成最终的请求URL才能访问(抓包的结果是最终的请求URL)。
现有解决方案
- Charles移动端、PC端抓包,获取公众号主页加密链接,再后续爬取。但得到的链接可能会失效,失效后还是需要人工手动去分析抓包结果,提取URL,无法做到自动化工程级别的微信爬虫。
- 登录自己公众号,通过“检索功能”爬取其他公众号的文章。但检索有上限,500篇一天。
- 八爪鱼模板傻瓜抓取。但高级功能收费,同样无法实现工程级别的部署,玩玩还是可以的 。
- 自动化测试工具(如Airtest,基于图像识别和poco控件识别的一款UI自动化测试工具)网易团队刚出的,挺新奇,移动端、PC都能抓,有些许bug,可以当做code版免费八爪鱼。同样不太适用于工程级别的部署。
遇到验证码的解决办法
正确的解决办法是识别并破解,但这里我们可以直接通过selenium截图保存本地,人肉识别并把结果输入值CommandLine,再把cookie记录下来维护个cookie池,以后换着用。不过这并不是一个永久的解决方法,只不过苦于时间紧迫,没时间去整验证码识别相关的东西,大家有时间可以学习下CNN训练验证码识别模型。如下实现思路是,判断返回页面中是否存在验证码,若有则down到本地,识别后命令行输入。
from splinter import Browser
from selenium import webdriver
def get_html(url):
with Browser("chrome", incognito=True,
headless=True) as browser:
browser.driver.set_page_load_timeout(400)
browser.driver.set_window_size(1200, 900)
browser.visit(url)
if "验证码" in browser.html:
now = datetime.datetime.now()
isValidCode = False
while isValidCode != True:
# screen shot
browser.screenshot("/home/jiangbowen/wechat_identifying_code/codeToBeDealWith%s.png" % now)
# manually input code
code = input("你gg了, please inpute code:")
code_imput = browser.find_by_id("seccodeInput")
code_imput[0].click()
code_imput[0].fill(code)
submit_btn = browser.find_by_id("submit")
time.sleep(3)
code_imput[0].click()
time.sleep(1)
code_imput[0].fill(code)
browser.screenshot("/home/jiangbowen/wechat_identifying_code/afterFillingCode%s.png" % now)
time.sleep(1)
submit_btn[0].click()
# be careful
time.sleep(7)
browser.screenshot("/home/jiangbowen/wechat_identifying_code/afterclick%s.png" % now)
print(browser.driver.current_url)
return browser.html
return browser.html
特别提醒:
在模拟填入验证码并点击提交后,需要我们等待一段时间,用于留给微信后端验证,此过程较长,建议5s以上(填了很久才发现这个鬼原因)。否则通过browser.driver.current_url获取到的仍然是验证页面的URL,而不是我们想要的检索页面。
每一步的ScreenShot如下:
可以看到验证成功!
保存Cookie的操作(建立Cookie池,略过此步亦可)
用pickle来实现cookie的存取,注意selenium在设置Cookie时需要先加载网站:
Login成功后保存Cookie:
import pickle
# save cookie after login
cookiess = browser.driver.get_cookies()
# overwrite
if not os.path.exists(LOCAL_COOKIE_PATH):
os.mkdir(LOCAL_COOKIE_PATH)
pickle.dump(cookiess, open(LOCAL_COOKIE_FILE, "wb"))
请求前读取Cookie:
LOCAL_COOKIE_PATH = "./cookies"
LOCAL_COOKIE_FILE = "./cookies/cookie_qingbo.pkl"
# add cookie
try:
# we should load website firstly
browser.driver.get("weixin.sogou.com")
loc_cookies = pickle.load(open(LOCAL_COOKIE_FILE, "wb"))
browser.driver.delete_all_cookies()
for cookie in loc_cookies:
cookie_dict = {}
cookie_dict['httpOnly'] = cookie.get('httpOnly')
# set other cookie items
browser.driver.add_cookie(cookie_dict)
browser.driver.refresh()
except Exception as e:
print("cookie parse error")
具体解析微信文章
根据公众号主页URL获取该页面下所有的文章Title与URL:
def get_titleurls_by_account_homepage_url(account_home_url):
"""
get_titles_urls_by_account_home_url
:param account_home_url: public account home page's url
:return: list of tuple (title, url)
"""
main_page_html = get_html(account_home_url)
account_name_reg = r"(?<=<title>)([\s\S]*?)(?=</title>)"
url_title_reg = r"<div class=\"weui_media_bd\">([\s\S]*?)</h4>"
url_sub_reg = r"(?<= hrefs=\")([\s\S]*?)(?=\">)"
title_sub_reg = r"=\">([\s\S]*?)(?=</h4>)" # need to remove top 3 char
account_name = re.findall(account_name_reg, main_page_html)[0]
url_title_arr = re.findall(url_title_reg, main_page_html)
title_url_list = []
for url_title in url_title_arr:
article_url = "https://mp.weixin.qq.com" + re.findall(url_sub_reg ,url_title)[0].replace(";" ,"&")
# remove <span> tag
article_title = re.sub(r"(?=<span)[\s\S]*?</span>", '', url_title).strip()
# remove HTML tags
article_title = re.sub(r"<[^>]+>", '', article_title).strip()
# remove blank
article_title = re.sub(r"\n* * * *", '', article_title).strip()
title_url_list.append((article_title, article_url))
return title_url_list
根据公众号文章链接解析内容
def scrape_url_content(url, title, account_name):
"""
get wechat article detail content
:param url: url
"""
html = get_html(url)
article = {}
try:
publish_time = re.findall(r"(?<=publish_time = \")[\s\S]*?(?=\")", html)[0]
# Html that we care about
tmp = re.findall('(?<=js_content">)[\s\S]*?(?=<script nonce)', html)[0]
# remove html tag except <img>
tmp = re.sub(r"<(?!img)[^>]*>", '', tmp)
# tmp = re.sub(r"(<(?!/?(p|img|span)(\s|>))[^>]*>)", '', tmp) # except p/img/span
# remove blank
tmp = re.sub(r"\n* * * *", '', tmp)
# remove <i> tags
tmp = re.sub(r"<i [\w].*?>", '', tmp)
cleaned_content_html = tmp
except Exception as e:
return
article["body"] = cleaned_content_html
article["title"] = title
article["source"] = account_name
article["publish_time"] = publish_time
return article
最终效果:
图片会保留原始标签,大家可自行处理。