使用代理反爬抓取微信文章,获取文章标题、内容、公众号等信息,并存储到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...