使用代理反爬抓取微信文章,獲取文章標題、內容、公衆號等信息,並存儲到MongoDB數據庫中。html
若是要抓取微信公衆號文章可使用搜狗的搜索引擎,它會顯示最新的文章,可是有兩個問題須要你們注意:python
關於代理池的實現以及使用能夠參考這篇文章:使用Redis+Flask維護動態代理池git
下圖展現了具體的流程框架:
github
def parse_index(html): doc = pq(html) items = doc('.news-box .news-list li .txt-box h3 a').items() for item in items: yield item.attr('href')def parse_index(html): doc = pq(html) items = doc('.news-box .news-list li .txt-box h3 a').items() for item in items: yield item.attr('href')
在流程框架部分咱們提到,在此將要使用搜狗搜索微信站點文章,首先讓咱們進入搜狗搜索界面https://weixin.sogou.com/
,好比輸入關鍵字風景
,就會出現微信文章的列表。
redis
從網頁的url能夠看出這是一個get請求,只保留主要參數,能夠把url簡化爲數據庫
其中,「query」表明搜索的關鍵詞,「type」表明搜索結果的類型,「type=1」表示搜索結果是微信公衆號,「type=2」表示搜索結果是微信文章,「page」也就是當前頁數。flask
分析完網頁的url組成以後,咱們先解決第一個問題:保存cookie,模擬登陸。
打開瀏覽器控制檯,選擇NetWork->Headers
選項卡,能夠看到請求的headers信息。瀏覽器
解決完以上問題以後,讓咱們嘗試寫一下代碼獲取第1-100頁的網頁源碼:服務器
from urllib.parse import urlencode import requests base_url = 'https://weixin.sogou.com/weixin?' # 構造請求頭 headers = { 'Cookie': 'CXID=DF1F2AE56903B8B6289106D60E0C1339; SUID=F5959E3D8483920A000000005BCEB8CD; sw_uuid=3544458569; ssuid=8026096631; pex=C864C03270DED3DD8A06887A372DA219231FFAC25A9D64AE09E82AED12E416AC; SUV=00140F4F78C27EE25BF168CF5C981926; ad=p7R@vkllll2bio@ZlllllVsE@EclllllNBENLlllll9lllllpA7ll5@@@@@@@@@@; IPLOC=CN4110; ABTEST=2|1543456547|v1; weixinIndexVisited=1; sct=1; JSESSIONID=aaaXtNmDWRk5X5sEsy6Cw; PHPSESSID=lfgarg05due13kkgknnlbh3bq7; SUIR=EF72CF750D0876CFF631992E0D94BE34;', 'Host': 'weixin.sogou.com', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36' } def get_html(url, count=1): response = requests.get(url, allow_redirects=False, headers=headers) # 判斷網頁返回的狀態碼是否正常 # 若是狀態碼是200說明能夠正常訪問 if response.status_code == 200: return response.text # 若是狀態碼是302,則說明IP已經被封 if response.status_code == 302: return None def get_index(keyword, page): data = { 'query': keyword, 'type': 2, 'page': page } queries = urlencode(data) url = base_url + queries html = get_html(url) return html def main(): for page in range(1, 101): html = get_index('風景', page) print(html) if __name__ == '__main__': main()
運行以上代碼,會發現剛開始運行正常,正確返回網頁源碼,以後便一直返回None,此時讓咱們打開瀏覽器觀察一下:微信
能夠看出,代碼運行後不停返回None的緣由是網頁被重定向,須要輸入驗證碼才能正常訪問,這即是咱們開篇說過的第二個問題,咱們的訪問被搜狗搜索的反爬蟲措施攔截,若是想要繼續正常訪問,便須要利用代理池獲取隨機代理來繞過反爬機制。
在使用Redis+Flask維護動態代理池一文中,咱們講解了代理池的基本原理和簡單實現,代碼已託管到github上,如今讓咱們利用代理池來獲取隨機代理。
首先讓咱們定義get_proxy()方法,返回代理池獲取的隨機可用ip:
# flask監聽的是5000端口 PROXY_POOL_URL = 'http://127.0.0.1:5000/get' def get_proxy(): try: response = requests.get(PROXY_POOL_URL) if response.status_code == 200: return response.text return None except ConnectionError: return None
接下來修改get_html(url, count=1)方法,以隨機ip的方式訪問網頁:
MAX_COUNT = 5 proxy = None def get_html(url, count=1): # 打印抓取的url print('Crawling', url) # 打印嘗試抓取的次數 print('Trying Count', count) global proxy # 若是抓取的次數大於最大次數,則返回None if count >= MAX_COUNT: print('Tried Too Many Counts') return None try: if proxy: proxies = { 'http': 'http://' + proxy } # allow_redirects=False:禁止瀏覽器自動處理302跳轉 response = requests.get(url, allow_redirects=False, headers=headers, proxies=proxies) else: response = requests.get(url, allow_redirects=False, headers=headers) if response.status_code == 200: return response.text # 狀態碼是302,說明IP已經被封,調用get_proxy()獲取新的ip if response.status_code == 302: # Need Proxy print('302') proxy = get_proxy() if proxy: print('Using Proxy', proxy) return get_html(url) else: print('Get Proxy Failed') return None except ConnectionError as e: # 若是鏈接超時,從新調用get_html(url, count)方法 print('Error Occurred', e.args) proxy = get_proxy() count += 1 return get_html(url, count)
再次運行代碼,會發現不停重複打印None的狀況基本消失。你們注意,這裏是基本消失,緣由是咱們的代理池使用的是免費代理網站獲取的代理,同一時刻可能會有許多人訪問,這樣就很容易形成ip地址被封的狀況。若是你想要獲取更好的效果,不妨使用一下收費代理。
至此,咱們解決了開篇提到的兩個問題,接下來,就能夠抓取網頁,分析內容。
首先咱們須要獲取到第1-100頁中每一篇文章的url:
def parse_index(html): doc = pq(html) items = doc('.news-box .news-list li .txt-box h3 a').items() for item in items: yield item.attr('href') def main(): for page in range(1, 101): html = get_index(KEYWORD, page) if html: article_urls = parse_index(html) print(article_urls)
獲取到每一篇文章的url以後,就須要解析每一篇文章的內容。解析方法與上面相同,在此再也不贅述。具體代碼以下:
def parse_detail(html): try: doc = pq(html) title = doc('.rich_media_title').text() content = doc('.rich_media_content ').text() date = doc('#publish_time').text() nickname = doc('.rich_media_meta_list .rich_media_meta_nickname').text() wechat = doc('#activity-name').text() return { 'title': title, 'content': content, 'date': date, 'nickname': nickname, 'wechat': wechat } except XMLSyntaxError: return None
須要注意的一點就是須要捕獲XMLSyntaxError
異常。
最後讓咱們新建一個config.py文件,文件中包含了MongoDB的URL,數據庫名稱,表名稱等常量:
MONGO_URL = 'localhost' MONGO_DB = 'weixin'
在spider.py中配置存儲到MongoDB相關方法:
from config import * import pymongo client = pymongo.MongoClient(MONGO_URL) db = client[MONGO_DB] def save_to_mongo(data): if db['articles'].update({'title': data['title']}, {'$set': data}, True): print('Saved to Mongo', data['title']) else: print('Saved to Mongo Failed', data['title'])
運行代碼,接下來在MongoDB中進行查看:
項目完整代碼已託管到github: https://github.com/panjings/p...