抓取微信公眾號的文章
壹.思路分析
目前所知曉的能夠抓取的方法有:
1、微信APP中微信公眾號文章鏈接的直接抓取(/s?__biz=MjM5MzU4ODk2MA==&mid=2735446906&idx=1&sn=ece37deaba0c8ebb9badf07e5a5a3bd3&scene=0#rd)
2、通過微信合作方搜狗搜索引擎(/),發送相應請求來間接抓取
第1種方法中,這種鏈接不太好獲取,而且他的規律不是特別清晰。
因此本文采用的是方法2----通過給 weixin.sogou.com 發送即時請求來實時解析抓取數據並保存到本地。
二.爬取過程
1、首先在搜狗的微信搜索頁面測試壹下,這樣能夠讓我們的思路更加清晰
在搜索引擎上使用微信公眾號英文名進行“搜公眾號”操作(因為公眾號英文名是公眾號唯壹的,而中文名可能會有重復,同時公眾號名字壹定要完全正確,不然可能搜到很多東西,這樣我們可以減少數據的篩選工作,只要找到這個唯壹英文名對應的那條數據即可),即發送請求到'/weixin?type=1&query=%s&ie=utf8&_sug_=n&_sug_type_= ' % ?'python',並從頁面中解析出搜索結果公眾號對應的主頁跳轉鏈接。
2.獲取主頁入口內容
使用request , urllib,urllib2,或者直接使用webdriver+phantomjs等都可以
這裏使用的是request.get()的方法獲取入口網頁內容
[python]?view plain?copy
#?爬蟲偽裝頭部設置?
self.headers?=?{'User-Agent':?'Mozilla/5.0?(Windows?NT?6.3;?WOW64;?rv:51.0)?Gecko/20100101?Firefox/51.0'}?
#?設置操作超時時長?
self.timeout?=?5?
#?爬蟲模擬在壹個request.session中完成?
self.s?=?requests.Session()?
[python]?view plain?copy
#搜索入口地址,以公眾為關鍵字搜索該公眾號?
def?get_search_result_by_keywords(self):?
self.log('搜索地址為:%s'?%?self.sogou_search_url)?
return?self.s.get(self.sogou_search_url,?headers=self.headers,?timeout=self.timeout).content?
3.獲取公眾號地址
從獲取到的網頁內容中,得到公眾號主頁地址, 這壹步驟有很多方法, beautifulsoup、webdriver,直接使用正則,pyquery等都可以
這裏使用的是pyquery的方法來查找公眾號主頁入口地址
[python]?view plain?copy
#獲得公眾號主頁地址?
def?get_wx_url_by_sougou_search_html(self,?sougou_search_html):?
doc?=?pq(sougou_search_html)?
#print?doc('p[class="tit"]')('a').attr('href')?
#print?doc('div[class=img-box]')('a').attr('href')?
#通過pyquery的方式處理網頁內容,類似用beautifulsoup,但是pyquery和jQuery的方法類似,找到公眾號主頁地址?
return?doc('div[class=txt-box]')('p[class=tit]')('a').attr('href')?
4.獲取公眾號主頁的文章列表
首先需要加載公眾號主頁,這裏用的是phantomjs+webdriver, 因為這個主頁的內容需要JS 渲染加載,采用之前的方法只能獲得靜態的網頁內容
[python]?view plain?copy
#使用webdriver?加載公眾號主頁內容,主要是js渲染的部分?
def?get_selenium_js_html(self,?url):?
browser?=?webdriver.PhantomJS()
browser.get(url)
time.sleep(3)
#?執行js得到整個頁面內容?
html?=?browser.execute_script("return?document.documentElement.outerHTML")?
return?html?
得到主頁內容之後,獲取文章列表,這個文章列表中有我們需要的內容
[python]?view plain?copy
#獲取公眾號文章內容?
def?parse_wx_articles_by_html(self,?selenium_html):?
doc?=?pq(selenium_html)?
print?'開始查找內容msg'?
return?doc('div[class="weui_media_box?appmsg"]')?
#有的公眾號僅僅有10篇文章,有的可能多壹點?
#return?doc('div[class="weui_msg_card"]')#公眾號只有10篇文章文章的?
5.解析每壹個文章列表,獲取我們需要的信息
6.處理對應的內容
包括文章名字,地址,簡介,發表時間等
7.保存文章內容
以html的格式保存到本地
同時將上壹步驟的內容保存成excel 的格式
8.保存json數據
這樣,每壹步拆分完,爬取公眾號的文章就不是特別難了。
三、源碼
第壹版源碼如下:
[python]?view plain?copy
#!/usr/bin/python?
#?coding:?utf-8?
import?sys?
reload(sys)?
sys.setdefaultencoding('utf-8')?
from?urllib?import?quote?
from?pyquery?import?PyQuery?as?pq?
from?selenium?import?webdriver?
import?requests?
import?time?
import?re?
import?json?
import?os?
class?weixin_spider:?
def?__init__(self,?kw):?
'?構造函數?'?
self.kw?=?kw?
#?搜狐微信搜索鏈接?
#self.sogou_search_url?=?'/weixin?type=1&query=%s&ie=utf8&_sug_=n&_sug_type_='?%?quote(self.kw)?
self.sogou_search_url?=?'/weixin?type=1&query=%s&ie=utf8&s_from=input&_sug_=n&_sug_type_='?%?quote(self.kw)?
#?爬蟲偽裝?
self.headers?=?{'User-Agent':?'Mozilla/5.0?(Windows?NT?10.0;?WOW64;?rv:47.0)?Gecko/20100101?FirePHP/0refox/47.0?FirePHP/0.7.4.1'}?
#?操作超時時長?
self.timeout?=?5?
self.s?=?requests.Session()?
def?get_search_result_by_kw(self):?
self.log('搜索地址為:%s'?%?self.sogou_search_url)?
return?self.s.get(self.sogou_search_url,?headers=self.headers,?timeout=self.timeout).content?
def?get_wx_url_by_sougou_search_html(self,?sougou_search_html):?
'?根據返回sougou_search_html,從中獲取公眾號主頁鏈接?'?
doc?=?pq(sougou_search_html)?
#print?doc('p[class="tit"]')('a').attr('href')?
#print?doc('div[class=img-box]')('a').attr('href')?
#通過pyquery的方式處理網頁內容,類似用beautifulsoup,但是pyquery和jQuery的方法類似,找到公眾號主頁地址?
return?doc('div[class=txt-box]')('p[class=tit]')('a').attr('href')?
def?get_selenium_js_html(self,?wx_url):?
'?執行js渲染內容,並返回渲染後的html內容?'?
browser?=?webdriver.PhantomJS()
browser.get(wx_url)
time.sleep(3)
#?執行js得到整個dom
html?=?browser.execute_script("return?document.documentElement.outerHTML")?
return?html?
def?parse_wx_articles_by_html(self,?selenium_html):?
'?從selenium_html中解析出微信公眾號文章?'?
doc?=?pq(selenium_html)?
return?doc('div[class="weui_msg_card"]')?
def?switch_arctiles_to_list(self,?articles):?
'?把articles轉換成數據字典?'?
articles_list?=?[]?
i?=?1?
if?articles:?
for?article?in?articles.items():?
self.log(u'開始整合(%d/%d)'?%?(i,?len(articles)))?
articles_list.append(self.parse_one_article(article))?
i?+=?1?
#?break?
return?articles_list?
def?parse_one_article(self,?article):?
'?解析單篇文章?'?
article_dict?=?{}?
article?=?article('.weui_media_box[id]')?
title?=?article('h4[class="weui_media_title"]').text()?
self.log('標題是:?%s'?%?title)?
url?=?''?+?article('h4[class="weui_media_title"]').attr('hrefs')?
self.log('地址為:?%s'?%?url)?
summary?=?article('.weui_media_desc').text()?
self.log('文章簡述:?%s'?%?summary)?
date?=?article('.weui_media_extra_info').text()?
self.log('發表時間為:?%s'?%?date)?
pic?=?self.parse_cover_pic(article)?
content?=?self.parse_content_by_url(url).html()?
contentfiletitle=self.kw+'/'+title+'_'+date+'.html'?
self.save_content_file(contentfiletitle,content)?
return?{?
'title':?title,?
'url':?url,?
'summary':?summary,?
'date':?date,?
'pic':?pic,?
'content':?content?
}?
def?parse_cover_pic(self,?article):?
'?解析文章封面圖片?'?
pic?=?article('.weui_media_hd').attr('style')?
p?=?re.compile(r'background-image:url(.?)')?
rs?=?p.findall(pic)?
self.log(?'封面圖片是:%s?'?%?rs[0]?if?len(rs)?>?0?else?'')?
return?rs[0]?if?len(rs)?>?0?else?''?
def?parse_content_by_url(self,?url):?
'?獲取文章詳情內容?'?
page_html?=?self.get_selenium_js_html(url)?
return?pq(page_html)('#js_content')?
def?save_content_file(self,title,content):?
'?頁面內容寫入文件?'?
with?open(title,?'w')?as?f:?
f.write(content)?
def?save_file(self,?content):?
'?數據寫入文件?'?
with?open(self.kw+'/'+self.kw+'.txt',?'w')?as?f:?
f.write(content)?
def?log(self,?msg):?
'?自定義log函數?'?
print?u'%s:?%s'?%?(time.strftime('%Y-%m-%d?%H:%M:%S'),?msg)?
def?need_verify(self,?selenium_html):?
'?有時候對方會封鎖ip,這裏做壹下判斷,檢測html中是否包含id=verify_change的標簽,有的話,代表被重定向了,提醒過壹陣子重試?'?
return?pq(selenium_html)('#verify_change').text()?!=?''?
def?create_dir(self):?
'創建文件夾'?
if?not?os.path.exists(self.kw):
os.makedirs(self.kw)
def?run(self):?
'?爬蟲入口函數?'?
#Step?0?:?創建公眾號命名的文件夾?
self.create_dir()?
#?Step?1:GET請求到搜狗微信引擎,以微信公眾號英文名稱作為查詢關鍵字?
self.log(u'開始獲取,微信公眾號英文名為:%s'?%?self.kw)?
self.log(u'開始調用sougou搜索引擎')?
sougou_search_html?=?self.get_search_result_by_kw()?
#?Step?2:從搜索結果頁中解析出公眾號主頁鏈接?
self.log(u'獲取sougou_search_html成功,開始抓取公眾號對應的主頁wx_url')?
wx_url?=?self.get_wx_url_by_sougou_search_html(sougou_search_html)?
self.log(u'獲取wx_url成功,%s'?%?wx_url)?
#?Step?3:Selenium+PhantomJs獲取js異步加載渲染後的html?
self.log(u'開始調用selenium渲染html')?
selenium_html?=?self.get_selenium_js_html(wx_url)?
#?Step?4:?檢測目標網站是否進行了封鎖?
if?self.need_verify(selenium_html):?
self.log(u'爬蟲被目標網站封鎖,請稍後再試')?
else:?
#?Step?5:?使用PyQuery,從Step?3獲取的html中解析出公眾號文章列表的數據?
self.log(u'調用selenium渲染html完成,開始解析公眾號文章')?
articles?=?self.parse_wx_articles_by_html(selenium_html)?
self.log(u'抓取到微信文章%d篇'?%?len(articles))?
#?Step?6:?把微信文章數據封裝成字典的list?
self.log(u'開始整合微信文章數據為字典')?
articles_list?=?self.switch_arctiles_to_list(articles)?
#?Step?7:?把Step?5的字典list轉換為Json?
self.log(u'整合完成,開始轉換為json')?
data_json?=?json.dumps(articles_list)?
#?Step?8:?寫文件?
self.log(u'轉換為json完成,開始保存json數據到文件')?
self.save_file(data_json)?
self.log(u'保存完成,程序結束')?
#?main?
if?__name__?==?'__main__':?
gongzhonghao=raw_input(u'輸入要爬取的公眾號')?
if?not?gongzhonghao:?
gongzhonghao='python6359'?
weixin_spider(gongzhonghao).run()?
第二版代碼:
對代碼進行了壹些優化和整改,主要:
1.增加了excel存貯
2.對獲取文章內容規則進行修改
3.豐富了註釋
本程序已知缺陷: 如果公眾號的文章內容包括視視頻,可能會報錯。
[python]?view plain?copy
#!/usr/bin/python?
#?coding:?utf-8?