原文連接html
個人GitHub博客地址node
分享一個筆者最近寫的 Python 相關的小 demo,爬取某個公衆號的全部歷史文章,並導出到本地,方便以後在線下環境直接觀看。參考了劉志軍的小冊基於Python實現微信公衆號爬蟲,有興趣的同窗也能夠自行購買。python
這個功能仍是有必定實際用途的,需求和功能雖然簡單明確,但我在開發的過程當中,也是遇到了必定的問題,能夠說好好的把 Python 爬蟲方面的知識複習了一遍。我也將從最基礎的抓包開始講起,但願能提供一個完整爬取流程的簡單教程。android
在 Windows 平臺,咱們常用 Fiddler 來進行抓包,筆者使用 Mac,因此比較習慣使用 Charles 來進行抓包。不只如此,這類工具在開發過程當中是很是重要的工具,筆者平時在客戶端開發過程當中,若是服務端接口還沒完成,只要定義好數據結構,經過這類工具的重定向功能,就能夠輕易的本身模擬數據來開發了。git
用手機抓取 https 接口,須要在手機裏安裝證書,網上方法不少,我這裏就不費篇幅了。github
咱們打開微信歷史消息界面,而後在 Charles 裏面找尋接口,經過觀察 Response 返回的內容,咱們發現了咱們須要的請求:web
咱們在 Python 中記錄下這個 url 和 Header,須要注意的是,這個url請求的數據只是第一頁的數據,上拉加載的url接口形式是徹底不一樣的。json
url = "https://mp.weixin.qq.com/mp/profile_ext?" \
"action=home&" \
"__biz=MjM5ODIyMTE0MA==&" \
"scene=124&" \
"devicetype=android-23&" \
"version=26060532&" \
"lang=zh_CN&" \
"nettype=WIFI&" \
"a8scene=3&" \
"pass_ticket=Pu%2FH3aPR7f%2FzgA52T%2Bv4fU9wSWkY5tgGGgAXWewji2dqpMDrjaxUbBR%2Fmo2e%2FaMX&wx_header=1"
headers = """ Host: mp.weixin.qq.com Connection: keep-alive User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; NX531J Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/044030 Mobile Safari/537.36 MicroMessenger/6.6.5.1280(0x26060532) NetType/WIFI Language/zh_CN x-wechat-key: b97b0a94956e0cb26093b03bcbfb059796e335db8c12a8036cdff0191103874cee2a5045062b4058d71c848ab74c8b256570c9a9547fe2eb9572b1a762f9cea43f91428b4a31bf5618a8c61c00da7287 x-wechat-uin: MTMzNjE3ODYyMQ%3D%3D Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,image/wxpic,image/sharpp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,en-US;q=0.8 Cookie: sd_userid=96301522585723838; sd_cookie_crttime=1522585723838; pgv_pvid=1151171120; tvfe_boss_uuid=92a513a6354c3896; rewardsn=; wxtokenkey=777; wxuin=1336178621; devicetype=android-23; version=26060532; lang=zh_CN; pass_ticket=Pu/H3aPR7f/zgA52T+v4fU9wSWkY5tgGGgAXWewji2dqpMDrjaxUbBR/mo2e/aMX; wap_sid2=CL3vkf0EEnBSWHFYWmVoZVpOMjU0cnBpSUhiLWF2cmZHVVVLVWZrWUp4QVRlclVVOTRwS1hmMGNUR0VJaXp1RlVzbGViM2wtZnVfakZVd21RcGxxbzI3U3R3cmtYYlUycXpLU0FzcGJFSm1ESkZsYVhzSzhBd0FBMLbQ5dcFOA1AlU4= Q-UA2: QV=3&PL=ADR&PR=WX&PP=com.tencent.mm&PPVN=6.6.5&TBSVC=43603&CO=BK&COVC=044030&PB=GE&VE=GA&DE=PHONE&CHID=0&LCID=9422&MO= NX531J &RL=1080*1920&OS=6.0.1&API=23 Q-GUID: 6a875f18ea5ba76bb6afb9ca13b788cb Q-Auth: 31045b957cf33acf31e40be2f3e71c5217597676a9729f1b """
複製代碼
這個請求返回的是一個 H5 界面,並非咱們指望的是一個 JSON 文件,但不要緊,經過咱們的 xml 解析器,咱們始終能夠經過細緻的觀察,找尋咱們須要的信息。咱們發現,下面這段代碼,隱藏着前十條文章的數據列表。微信
def extract_data(html_content):
rex = "msgList = '({.*?})'"
pattern = re.compile(pattern=rex, flags=re.S)
match = pattern.search(html_content)
if match:
data = match.group(1)
data = html.unescape(data)
data = json.loads(data)
articles = data.get("list")
articles_lists = dict()
for item in articles:
if item.get("app_msg_ext_info"):
articles_lists[item["app_msg_ext_info"]["title"]] = item["app_msg_ext_info"]["content_url"]
return articles_lists
複製代碼
咱們能夠經過正則來提取出這部分數據,保存在 json 中,如今讓咱們來分析下數據:cookie
article = {'app_msg_ext_info':
{'title': '那些對印度的誤解與偏見',
'copyright_stat': 11,
'is_multi': 1,
'content': '',
'author': 'WeaponZhi',
'subtype': 9,
'del_flag': 1,
'fileid': ,
'content_url': 'http:\\/\\/mp....',
''
'digest': '提到印度,你首先會想到什麼',
'cover': 'http:\\/\\/mmbiz.qpic.cn\\...',
'multi_app_msg_item_list': [{'fileid': 861719336,
'content_url': 'http:\\/\\/mp...',
'content': '', 'copyright_stat': 11,
'cover': 'http:\\/\\/mmbiz.qpic.cn',
'del_flag': 1,
'digest': '攜程再努努力就快遇上百度了',
'...
複製代碼
根據咱們歷史文章的樣式,以及咱們須要的數據和需求,筆者抽取了每篇文章的幾個重要字段:
multi_app_msg_item_list
字段就是一組多圖文數據列表,在微信裏的展示形式是這樣的
如今咱們拿到了每篇文章的具體連接 content_url,後面咱們須要作的就是請求這個url,從中抽取文章內容,再把內容以必定的格式保存在文件中便可。
headers = headers_to_dict(headers)
response = requests.get(url, headers=headers, verify=False)
if '<title>驗證</title>' in response.text:
raise Exception("獲取微信公衆號文章失敗,多是由於你的請求參數有誤,請從新獲取")
data = extract_data(response.text)
rex = r'\\/'
for item in data:
pattern = re.sub(rex, '/', html.unescape(data.get(item)))
response = requests.get(pattern, headers=headers, verify=False)
parser_text_to_file(item, response.text)
def parser_text_to_file(title, article_content):
soup = BeautifulSoup(article_content, 'html.parser', from_encoding='utf8')
node = soup.find(id="js_content")
write_text_to_file(title, node)
def write_text_to_file(title, node):
contents = node.descendants
for item in contents:
if isinstance(item, NavigableString):
with open(title, "a", encoding="utf-8") as f:
f.write(str(item))
f.write('\n\n')
複製代碼
這裏咱們寫了一個驗證判斷,爲了防止過分的爬取操做,爬取歷史文章的接口 Cookie 只能有必定的有效時間,若是你在爬取過程當中發現獲取數據失敗,那你就須要從新進入界面而後更新代碼中的 Header 中的Cookie了,咱們在後面的文章中對這個問題將進行具體解析。
實際上上面的代碼就是這個小 demo 的業務核心了,咱們如今只處理了歷史文章前十篇的內容,下篇文章我將經過加載更多接口,將一個公衆號全部文章爬取出來,更有意思的天然還在後面。
推薦閱讀