使用代理處理反爬抓取微信文章

目標

使用代理反爬抓取微信文章,獲取文章標題、內容、公衆號等信息,並存儲到MongoDB數據庫中。html

流程框架

若是要抓取微信公衆號文章可使用搜狗的搜索引擎,它會顯示最新的文章,可是有兩個問題須要你們注意:python

  • 若是要抓取某一個主題(好比微信風景文章)的全部記錄的話,須要先登陸(也就是你的請求頭headers中要有登錄以後服務器返回的cookies),未登陸只能夠查看10頁,登陸以後能夠查看100頁
  • 搜狗微信站點的反爬措施比較嚴格,若是隻是用本地IP(單IP)去抓取的話確定是不行的,這個時候咱們須要用到代理池技術(經過可用隨機代理去繞過反爬機制)

關於代理池的實現以及使用能夠參考這篇文章:使用Redis+Flask維護動態代理池git

下圖展現了具體的流程框架:
github

(1)抓取索引頁內容

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的緣由是網頁被重定向,須要輸入驗證碼才能正常訪問,這即是咱們開篇說過的第二個問題,咱們的訪問被搜狗搜索的反爬蟲措施攔截,若是想要繼續正常訪問,便須要利用代理池獲取隨機代理來繞過反爬機制。

(2)代理設置

使用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地址被封的狀況。若是你想要獲取更好的效果,不妨使用一下收費代理。

至此,咱們解決了開篇提到的兩個問題,接下來,就能夠抓取網頁,分析內容。

(3)分析詳情頁內容

首先咱們須要獲取到第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異常。

(4)將數據保存到數據庫

最後讓咱們新建一個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...
相關文章
相關標籤/搜索