1. 程式人生 > >python實戰筆記之(6):使用代理處理反爬抓取微信文章

python實戰筆記之(6):使用代理處理反爬抓取微信文章

搜狗(http://weixin.sogou.com/)已經為我們做了一層微信文章的爬取,通過它我們可以獲取一些微信文章的列表以及微信公眾號的一些資訊,但是它有很多反爬蟲的措施,可以檢測到你的IP異常,然後把你封掉。本文采用代理的方法處理反爬來抓取微信文章。

(1)目標站點分析

開啟搜狗微信,輸入要查詢的內容,比如我們輸入“風景”,就會出現微信文章的列表,向下翻動我們可以發現每頁有10條內容,在最下方可以進行翻頁。需要注意的是,未登陸時最多可以檢視10頁內容,登陸之後就可以檢視100頁的內容(也就是說,做爬蟲的時候可以使用cookie爬取到100頁內容):

從網頁的url可以看出這是一個get請求,只保留主要的請求引數,把url簡化為:

其中,“query”代表搜尋的關鍵詞,“type”代表搜尋結果的型別,“type=1”表示搜尋結果是微信公眾號,“type=2”表示搜尋結果是微信文章,“page”也就是當前頁數。

現在網頁是能正常訪問的,但當我們點選翻頁比較頻繁的話,就會出現訪問出錯,輸入驗證碼之後才能再次正常訪問:

對網頁請求進行分析,可以發現正常請求時狀態碼都是200,出現訪問出錯時狀態碼變成了302:

狀態碼302又代表什麼呢?百度一下:

HTTP狀態碼302表示臨時性重定向,該狀態碼錶示請求的資源已被分配了新的URI,希望使用者(本次)能使用新的URI訪問。

也就是說,我們原來的請求被跳轉到了一個新的頁面,這個新的頁面就是訪問出錯,需要輸入驗證碼的頁面:

根據以上分析,我們可以根據請求返回的狀態碼判斷IP是否被封,如果狀態碼是200說明可以正常訪問,如果狀態碼是302,則說明IP已經被封。

接著我們再分析一下後續的頁面。在列表裡開啟一條微信文章,我們可以獲取到文章的標題、公眾號的一些資訊,以及文章內容。文章內容包括文字和一些圖片,在這裡我們只爬取文章中的文字部分。

(2)流程框架

1.抓取索引頁內容

利用requests請求目標站點,得到索引網頁HTML程式碼,返回結果。

2.代理設定

如果遇到302狀態碼,則說明IP被封,切換代理重試。

3.分析詳情頁內容

請求詳情頁,分析得到標題、正文等內容。

4.將資料儲存到資料庫

將結構化資料儲存至MongoDB。

(3)爬蟲程式碼

# weixin_article.py

import re
import requests
from urllib.parse import urlencode
from pyquery import PyQuery as pq
from requests.exceptions import ConnectionError
from weixin_article_config import *
import pymongo
from fake_useragent import UserAgent, FakeUserAgentError
import time

client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]

proxy = None  # 全域性代理


def get_proxy():
	try:
		response = requests.get('http://127.0.0.1:5000/get')
		if response.status_code == 200:
			return response.text
		return None
	except ConnectionError:
		return None


def get_html(url, count=1):
	print('Crawling', url)
	print('Trying Count', count)
	global proxy  # 引用全域性變數
	if count >= MAX_COUNT:
		print('Tried Too Many Counts')
		return None
	try:
		ua = UserAgent()
	except FakeUserAgentError:
		pass
	headers = {
		'Cookie': 'IPLOC=CN3203; SUID=4761C3782E08990A000000005B496B2A; SUV=1531538220705353; ABTEST=0|1531538246|v1; weixinIndexVisited=1; JSESSIONID=aaa5HdENmLh-idG2g6isw; PHPSESSID=ctkdrtb5sai55cpcd99uglsit4; SUIR=6D964C6E1B1E6F17ABEAE3C31C02C20B; ppinf=5|1533879269|1535088869|dHJ1c3Q6MToxfGNsaWVudGlkOjQ6MjAxN3x1bmlxbmFtZTo1OnN1Z2FyfGNydDoxMDoxNTMzODc5MjY5fHJlZm5pY2s6NTpzdWdhcnx1c2VyaWQ6NDQ6bzl0Mmx1QXg2cUNnY3dESUlCVkQ4REQzejdmVUB3ZWl4aW4uc29odS5jb218; pprdig=qmTYp6UdjuqBQsh41S2iPuC6aVGbF8-gC-oD4_JnXCEABwuE8dKqwUYrBA6ShYaZPxoNW11zcC9vnHQXr57mKAkCYl_j7HUAwAwPPkB8Hw8Iv1IBDQKe3oFFlmig9sp8N_H9VHyF9G-o03WmzDLDoZyiZ-3MPvM-olyDPQ4j_gY; sgid=31-36487535-AVttIibUibd0Gctt8SOaDTEqo; sct=4; wP_h=effa146bd88671d4ec8690f70eefe1957e085595; ppmdig=1533917694000000a1a24383d97adaba971d77d02f75fa59; SNUID=9028BCC8F9FC8BEEF42ED05AF94BDD9E; seccodeRight=success; successCount=1|Fri, 10 Aug 2018 16:20:22 GMT',
		'Host': 'weixin.sogou.com',
		'Referer': 'http://weixin.sogou.com',
		'Upgrade-Insecure-Requests': '1',
		'User-Agent': ua.random
	}
	try:
		if proxy:
			proxies = {'http': proxy}
			response = requests.get(url, allow_redirects=False, headers=headers, proxies=proxies)  # timeout=10
		else:
			response = requests.get(url, allow_redirects=False, headers=headers)  # timeout=10
		if response.status_code == 200:
			return response.text
		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
		else:
			print('Error Status Code', response.status_code)
			return None
	except ConnectionError as e:
		print('Error Occurred', e.args)
		proxy = get_proxy()
		count += 1
		return get_html(url, count)


def get_index(keyword, page):
	data = {
		'query': keyword,
		'type': '2',
		'page': page
	}
	url = 'http://weixin.sogou.com/weixin?' + urlencode(data)
	return get_html(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 get_detail(url):
	try:
		response = requests.get(url)
		if response.status_code == 200:
			return response.text
		return None
	except ConnectionError:
		return None


def parse_detail(html, url):
	doc = pq(html)
	title = doc('.rich_media_title').text()
	content = "".join(doc('.rich_media_content').text().split())
	nickname = doc('.profile_nickname').text()
	wechat = doc('#js_profile_qrcode > div > p:nth-child(3) > span').text()
	date_pattern = re.compile('var publish_time = \"(.*?)\"', re.S)
	date_search = re.search(date_pattern, html)
	if date_search:
		date = date_search.group(1)
	else:
		date = None
	return {
		'url': url,
		'title': title,
		'date': date,
		'nickname': nickname,
		'wechat': wechat,
		'content': content
	}


def save_to_mongo(data):
	if db['articles'].update({'title': data['title']}, {'$set': data}, True):
		print('Save to Mongo', data['title'])
		return True
	else:
		print('Save to Mongo Failed', data['title'])
		return False


def main():
	for page in range(1, 101):
		html = get_index(KEYWOED, page)
		time.sleep(1)  # 減小IP被封的風險
		if html:
			article_urls = parse_index(html)
			for article_url in article_urls:
				if article_url:
					article_html = get_detail(article_url)
					if article_html:
						article_data = parse_detail(article_html, article_url)
						if article_data:
							save_to_mongo(article_data)


if __name__ == '__main__':
	main()
# config.py

MONGO_URL = 'localhost'
MONGO_DB = 'weixin'
MONGO_TABLE = 'articles'

KEYWOED = '風景'  # 關鍵詞
MAX_COUNT = 5  # 最大請求次數

此外,需要注意的是,程式使用的代理是從自己維護的代理池中取用的,而代理池是從網上獲取的一些公共IP,這些IP一般是不太穩定的,也有可能是很多人都在使用的,所以爬取效果可能不是很好,被禁的概率比較高。當然,你也可以使用自己購買的一些代理,只需要改寫“get_proxy”方法就ok了。

最後,還需要注意Cookies的一些設定。從下面的圖中我們可以看出,cookies是有它自己的過期時間的,如果在抓取過程中發現無法獲取後面的內容,有可能是cookies已經過期了,這時候我們重新登入一下,然後替換掉原來的Cookies就好了。