参考:崔庆才老师教程
我们将从搜狗-微信这个网址来爬取微信的文章。
https://weixin.sogou.com/
输入“程序员”并搜索:
可以看到上方的URL有许多的信息,我们只保留query、type、page这几个参数即可。修改page可以实现翻页。
ps:登录后才能查看第10页之后的内容
打开审查->Network->勾选Preserve,然后疯狂翻页,不久后就会跳转到这样的页面:
这说明大量翻页触发了网站的反爬虫措施,导致ip被封,需要进行解锁。
然而从doc中可以看到,请求失败的那页(状态码应该非200)被隐藏,只留下了状态码为200的这个验证页面。但是实际上,最后的那次请求返回状态码是302。
302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。
再分析一下网页的结构:
在搜索结果的页面中,选择标题这个元素,可以看到这篇文章的URL。点击进去后,就会跳转到微信的页面了。
抓取索引页内容
利用requests请求目标站点,得到索引网页HTML代码,返回结果。
代理设置(关键)
如果遇到302状态码,则证明IP被封,切换代理重试。
分析详情页内容
请求详情页 ,分析得到标题,正文等内容
将数据保存到MongoDB
首先编写获取索引页的代码并测试:
from urllib.parse import urlencode
import requests
from requests.exceptions import ConnectionError
base_url = 'https://weixin.sogou.com/weixin?'
headers={'Cookie': 'IPLOC=CN3205; SUID=E5C695243118960A000000005BD115E8; ld=Tlllllllll2b1SgolllllVsSVWklllllJJgpiyllll9lllllpylll5@@@@@@@@@@; SUV=1540429307839674; ABTEST=4|1541298543|v1; weixinIndexVisited=1; JSESSIONID=aaaKxagg6ZBOkf5LLDaBw; sct=2; ppinf=5|1541299811|1542509411|dHJ1c3Q6MToxfGNsaWVudGlkOjQ6MjAxN3x1bmlxbmFtZToyNzolRTclOEMlQUElRTUlODYlQjIlRTUlODclODl8Y3J0OjEwOjE1NDEyOTk4MTF8cmVmbmljazoyNzolRTclOEMlQUElRTUlODYlQjIlRTUlODclODl8dXNlcmlkOjQ0Om85dDJsdU9wakYzNVp1NklhNGhqYTdKUUxydTRAd2VpeGluLnNvaHUuY29tfA; pprdig=FzBX9Lki68sfImndi44lcV84vLEqbuPe8AXYRZYh5DtlawPVJEYr3bvv1oF8vmRfP0_rrTGYvtpqKwb39yNvJWqXl-Oh-29iaP0S893esgJdg2XNaxk7PNy5dcq1gMZOmf2kS_2YjNbV8WDULQnpjleCUcqcMMw3Av-FlSTgeh4; sgid=19-37785553-AVveXmPrwZ6BLoWTJ85UWicI; ppmdig=1541299811000000f0314597e0483df6fc4b14c41cacb024; PHPSESSID=0t7h64pmb3n0iphtp2j62i3a26; SUIR=A278AA1A3F3B46F2B8CFF48F3FD5AB76; SNUID=8ED5863612176BDC72510ED513A5E096',
'Host': 'weixin.sogou.com',
'Referer': 'https://weixin.sogou.com/weixin?query=%E7%A8%8B%E5%BA%8F%E5%91%98&type=2&page=39&ie=utf8',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
}
#请求url,这部分需要多次使用,所以单独写为一个函数
def get_html(url):
try :
response = requests.get(url, allow_redirects = False,headers=headers)#禁止自动处理跳转
if response.status_code == 200:
return response.text
if response.status_code == 302:
# 需要代理
pass
except ConnectionError:
return get_html(url) #若失败,重试
#获取索引页
def get_index(keyword,page):
data = { #将get请求所需要的参数构造为字典
'query':keyword,
'type':2,
'page':page
}
queries = urlencode(data)
url = base_url + queries #完成url的拼接
html = get_html(url)#调用请求页面的函数
print(html)
if __name__ == '__main__':
get_index('程序员',1)
上面的代码是非常好理解的。url采用拼接的方法,将前面固定的部分与后面的参数拼接起来就是我们所需要的,例如:https://weixin.sogou.com/weixin?query=程序员&type=2&page=1 这个url。
headers的部分,可以从网页审查中复制过来,并且构造为一个字典:
运行程序后可以看到,第一页的html已经被获取了:
由于只请求了第一页,并不会触发反爬虫措施,那么我们将代码完善,使程序循环地请求网页:
keyword = "程序员"
#请求url
def get_html(url):
try :
response = requests.get(url, allow_redirects = False,headers=headers)#禁止自动处理跳转
if response.status_code == 200:
return response.text
if response.status_code == 302:
# 需要代理
print("302!")
except ConnectionError:
return get_html(url) #若失败,重试
#获取索引页
def get_index(keyword,page):
data = { #将get请求所需要的参数构造为字典
'query':keyword,
'type':2,
'page':page
}
queries = urlencode(data)
url = base_url + queries #完成url的拼接
html = get_html(url)
return html
def main():
for page in range(1,101):
html = get_index(keyword,page)
print(html)
if __name__ == '__main__':
main()
运行后可以看到,起初还能正常请求,继而就报出了302信息:
这就说明此时的ip已经被封了。
出现ip被封的情况,我们就需要更换代理进行请求。
proxy_pool_url = 'http://127.0.0.1:5000/get' #这是从web接口获取代理的地址
proxy = None #将代理设为全局变量
max_count = 5 #最大请求次数
#获取代理
def get_proxy():
try:
response = requests.get(proxy_pool_url)
if response.status_code == 200:
return response.text
return None
except ConnectionError:
return None
#请求url
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 :
if proxy:# 如果现在正在使用代理
proxies = {
'http:':'http://'+ proxy #设置协议类型
}
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
if response.status_code == 302:
# 需要代理
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:
print('Error Occurred',e.args)#打印错误信息
proxy = get_proxy() #若失败,更换代理
count += 1 #请求次数+1
return get_html(url,count) #重试
运行前需要将代理池打开,详情可参考上一篇文章:如何用Flask和Redis维护代理池。
运行后:
可以看到程序在不断尝试使用新的代理进行请求。
但是很不幸,尝试的代理,没有一次是请求成功的…估计是这些免费的代理太多人在用了,又或者是搜狗的反爬变严格了。
# 解析索引页
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')
这一步很简单,通过Pyquery的标签选择器就可以完成了。
#请求详情页
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):
doc = pq(html)
title = doc('.rich_media_title').text()
content = doc('.rich_media_content').text()
date = doc('#post-date').text()
nickname = doc('#js_profile_qrcode > div > strong').text()
wechat = doc('#js_profile_qrcode > div > p:nth-child(3) > span').text()
return {
'title':title,
'content':content,
'date':date,
'nickname':nickname,
'wechat':wechat
}
这一步也没啥好说的,直接传标签选择器就完事儿了。
client = pymongo.MongoClient('localhost')
db = client["weixin"]
def save_to_mongo(data):
if db['articles'].update({'title':data['title']},{'$set':data},True):#如果不存在则插入,否则进行更新
print('save to Mongo',data["title"])
else:
print('Save to Monge Failed',data['title'])
from urllib.parse import urlencode
import requests
from lxml.etree import XMLSyntaxError
from requests.exceptions import ConnectionError
from pyquery import PyQuery as pq
import pymongo
client = pymongo.MongoClient('localhost')
db = client["weixin"]
base_url = 'https://weixin.sogou.com/weixin?'
headers={'Cookie': 'IPLOC=CN3205; SUID=E5C695243118960A000000005BD115E8; ld=Tlllllllll2b1SgolllllVsSVWklllllJJgpiyllll9lllllpylll5@@@@@@@@@@; SUV=1540429307839674; ABTEST=4|1541298543|v1; weixinIndexVisited=1; JSESSIONID=aaaKxagg6ZBOkf5LLDaBw; sct=2; ppinf=5|1541299811|1542509411|dHJ1c3Q6MToxfGNsaWVudGlkOjQ6MjAxN3x1bmlxbmFtZToyNzolRTclOEMlQUElRTUlODYlQjIlRTUlODclODl8Y3J0OjEwOjE1NDEyOTk4MTF8cmVmbmljazoyNzolRTclOEMlQUElRTUlODYlQjIlRTUlODclODl8dXNlcmlkOjQ0Om85dDJsdU9wakYzNVp1NklhNGhqYTdKUUxydTRAd2VpeGluLnNvaHUuY29tfA; pprdig=FzBX9Lki68sfImndi44lcV84vLEqbuPe8AXYRZYh5DtlawPVJEYr3bvv1oF8vmRfP0_rrTGYvtpqKwb39yNvJWqXl-Oh-29iaP0S893esgJdg2XNaxk7PNy5dcq1gMZOmf2kS_2YjNbV8WDULQnpjleCUcqcMMw3Av-FlSTgeh4; sgid=19-37785553-AVveXmPrwZ6BLoWTJ85UWicI; PHPSESSID=0t7h64pmb3n0iphtp2j62i3a26; SUIR=A278AA1A3F3B46F2B8CFF48F3FD5AB76; SNUID=5E6D3E8EABAFD279CA76D65AAB866BC9; ppmdig=154133180100000026c4f7c59d4fcd1c4b8140c7bf7429d3',
'Host': 'weixin.sogou.com',
'Referer': 'https://weixin.sogou.com/weixin?query=%E7%A8%8B%E5%BA%8F%E5%91%98&type=2&page=10&ie=utf8',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
}
keyword = "程序员"
proxy_pool_url ='http://127.0.0.1:5010/get' #这是从web接口获取代理的地址
proxy = None #将代理设为全局变量
max_count = 3 #最大请求次数
#获取代理
def get_proxy():
try:
response = requests.get(proxy_pool_url)
if response.status_code == 200:
return response.text
return None
except ConnectionError:
return None
#请求url
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 :
if proxy:# 如果现在正在使用代理
proxies = {
'http':'http://'+ proxy #设置协议类型
}
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:
print("请求成功!")
return response.text
if response.status_code == 302:
# 需要代理
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:
print('Error Occurred',e.args)#打印错误信息
proxy = get_proxy() #若失败,更换代理
count += 1 #请求次数+1
return get_html(url,count) #重试
#获取索引页
def get_index(keyword,page):
data = { #将get请求所需要的参数构造为字典
'query':keyword,
'type':2,
'page':page
}
queries = urlencode(data)
url = base_url + queries #完成url的拼接
html = get_html(url)
if html:
print('第'+str(page)+'页获取成功!')
return html
# else:
# return get_index(keyword,page)
# 解析索引页
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):
try:
doc = pq(html)
title = doc('.rich_media_title').text()
content = doc('.rich_media_content').text()
date = doc('#post-date').text()
nickname = doc('#js_profile_qrcode > div > strong').text()
wechat = doc('#js_profile_qrcode > div > p:nth-child(3) > span').text()
return {
'title':title,
'content':content,
'date':date,
'nickname':nickname,
'wechat':wechat
}
except XMLSyntaxError:
return None
def save_to_mongo(data):
if db['articles'].update({'title':data['title']},{'$set':data},True):#如果不存在则插入,否则进行更新
print('save to Mongo',data["title"])
else:
print('Save to Monge Failed',data['title'])
def main():
for page in range(1,101):
html = get_index(keyword,page)
if html:
article_urls = parse_index(html)
for article_url in article_urls:
article_html = get_detail(article_url)
if article_html:
article_data = parse_detail(article_html)
print(article_data)
if article_data:
save_to_mongo(article_data)
if __name__ == '__main__':
main()
本文主要是熟悉一下代理的设置。
需要注意的是,程序使用的代理是从自己维护的代理池中取用的(使用的是github大佬的代码),而代理池是从网上获取的一些公共IP,这些IP一般是不太稳定的,也有可能是很多人都在使用的,所以爬取效果可能不是很好,被禁的概率那是非常的高。当然,你也可以使用自己购买的一些代理,只需要改写“get_proxy”方法就ok了。
最后,还需要注意Cookies的一些设置。cookies是过期时间的,如果在抓取过程中发现无法获取后面的内容,有可能是cookies已经过期了,这时候我们重新登录一下,然后替换掉原来的Cookies就好了。