GitHub:
前言
上节我们爬完了所有索引页,这节我们开始爬取公众号文章。
要知道,我们之前爬的是 weixin.sogou.com
,而现在爬取的是 mp.weixin.qq.com
,这是两个完全不同的网站。
关于 mp.weixin.qq.com
的反爬,并不是很严厉,我们只需需要构造新的请求头。
硬说有的话,那就是:首先,我们爬取的 requests 队列里的 URL 链接有时效性,争取在 12 小时内使用。
其次,注意到上面的链接,都是 http
开头,而正式请求时的链接都是 https
开头。这时我们要打开我们 GET 请求的重定向开关。
清洗代理
对,又要清洗代理,不要觉得这步很繁琐,这是提高我们爬虫体验极其关键的一步。
打开我们的代理池文件夹 proxypool
,找到里面的 setting.py
,
把 TEST_URL
改成 “https://mp.weixin.qq.com/”,
把 REDIS_KEY
改成 “weixinproxies”。
爬取微信公众号文章
在运行代理池后,我们积累足够多能爬取 mp.weixin.qq.com
的健康代理。
在上节提到的 db.py
文件中,加入 weixin_proxy_random()
和 weixin_proxy_decrease()
两个函数。
目的是,便于提取 ”weixinproxies“里的代理。
db.py
import redis
from random import choice
from weixin.error import PoolEmptyError
# Redis数据库地址
REDIS_HOST = 'localhost'
# Redis端口
REDIS_PORT = 6379
# Redis密码,如无填None
REDIS_PASSWORD = None
WEIXIN_PROXY_REDIS_KEY = 'weixinproxies'
#最大评分
MAX_SCORE = 100
MIN_SCORE = 0
class RedisClient(object):
def __init__(self, host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD):
"""
初始化
:param host: Redis 地址
:param port: Redis 端口
:param password: Redis密码
"""
self.db = redis.StrictRedis(host=host, port=port, password=password, decode_responses=True)
def weixin_proxy_random(self):
"""
随机获取有效代理,首先尝试获取最高分数代理,如果不存在,按照排名获取,否则异常
:return: 随机代理
"""
result = self.db.zrangebyscore(WEIXIN_PROXY_REDIS_KEY, MAX_SCORE, MAX_SCORE)
if len(result):
return choice(result)
else:
result = self.db.zrevrange(WEIXIN_PROXY_REDIS_KEY, 0, 100)
if len(result):
return choice(result)
else:
raise PoolEmptyError
def weixin_proxy_decrease(self, proxy):
"""
代理值减一分,小于最小值则删除
:param proxy: 代理
:return: 修改后的代理分数
"""
score = self.db.zscore(WEIXIN_PROXY_REDIS_KEY, proxy)
if score and score > MIN_SCORE:
print('代理', proxy, '当前分数', score, '减1')
return self.db.zincrby(WEIXIN_PROXY_REDIS_KEY, proxy, -1)
else:
print('代理', proxy, '当前分数', score, '移除')
return self.db.zrem(WEIXIN_PROXY_REDIS_KEY, proxy)
接下来我们创建一个名为 articles.py
的文件。
articles.py
import requests
from weixin.db import RedisClient
from weixin.mysql import MySQL
from pyquery import PyQuery as pq
#合法状态码
VALID_STATUSE = [200]
class Articles():
headers = {
'Host': 'mp.weixin.qq.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0',
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://mp.weixin.qq.com/s?src=11×tamp=1543224952&ver=1268&signature=n0EW*NEa73Cd39RmRKfaYPU5NUDuN5X6eypDap*--nQ913dIIe3i8EcRnyd7PptsjOAKzDVuI*ikSsioBg0*zMGPbB27CUrORDvEMav2hvZHp2tFF3V4cNyl09Cr73Rl&new=1',
'Cookie': 'rewardsn=; wxtokenkey=777',
'Connection': 'keep-alive',
}
redis = RedisClient()
mysql = MySQL()
proxies = None
def test_proxy(self):
"""
二次清洗代理
:return:
"""
global proxies
url = 'https://mp.weixin.qq.com'
proxy = self.redis.weixin_proxy_random()
proxies = {
'http': 'http://' + proxy,
'https': 'https://' + proxy
}
try:
r = requests.get(url, headers=self.headers, allow_redirects=False, proxies=proxies, timeout=30)
if r.status_code == 200:
pass
else:
self.redis.weixin_proxy_decrease(proxy)
self.test_proxy()
except:
self.redis.weixin_proxy_decrease(proxy)
self.test_proxy()
def start(self):
global proxies
while not self.redis.request_empty():
url = self.redis.request_pop()
try:
response = requests.get(url, headers=self.headers, proxies=proxies, allow_redirects=True, timeout=20)
print('正在爬取:', url)
print(response.status_code)
if response and response.status_code in VALID_STATUSE:
print('status_code:200')
self.parse_detail(response)
else:
self.test_proxy()
self.redis.request_add(url)
except:
self.test_proxy()
self.redis.request_add(url)
def parse_detail(self, response):
"""
解析详情页
:param response: 响应
:return: 微信公众号文章
"""
doc = pq(response.text)
data = {
'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()
}
if not len(data) == 0:
self.mysql.insert('articles', data)
函数 parse_detail()
在接收 response
后,用 PyQuery 解析,再把数据保存到 Mysql。
在编写 mysql.py
之前,我们要先配置好自己的 Mysql,创建一个名为 “weixin”的数据库,还有一系列参数。
然后我们还有在数据库 “weixin”中创建一个名为 “articles”的数据表,SQL 语句:
CREATE TABLE 'articles' (
'title' varchar(255) NOT NULL,
'content' text NOT NULL,
'date' varchar(255) NOT NULL,
'wechat' varchar(255) NOT NULL,
'nickname' varchar(255) NOT NULL,
)DEFAULT CHARSET=utf8;
mysql.py
import pymysql
MYSQL_HOST = 'localhost'
MYSQL_PORT = 3306
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'
MYSQL_DATABASE = 'weixin'
class MySQL():
def __init__(self, host=MYSQL_HOST, username=MYSQL_USER, password=MYSQL_PASSWORD, port=MYSQL_PORT,
database=MYSQL_DATABASE):
"""
MySQL初始化
:param host:
:param username:
:param password:
:param port:
:param database:
"""
try:
self.db = pymysql.connect(host, username, password, database, charset='utf8', port=port)
self.cursor = self.db.cursor()
except pymysql.MySQLError as e:
print(e.args)
def insert(self, table, data):
"""
插入数据
:param table:
:param data:
:return:
"""
keys = ', '.join(data.keys())
values = ', '.join(['%s'] * len(data))
sql_query = 'insert into %s (%s) values (%s)' % (table, keys, values)
try:
self.cursor.execute(sql_query, tuple(data.values()))
self.db.commit()
print('存入mysql')
except pymysql.MySQLError as e:
print(e.args)
self.db.rollback()
至此,爬取搜狗微信文章就算完成。我们就有超多文章可以看了,
然而,并没有太大卵用。
总结
代码性能
在爬取速率上,我并没有采取一些加速的方法,比如异步协程等等。
因为我害怕触发更加强大的反爬。
毕竟爬和反爬就是“道高一尺,魔高一丈”的事情,既然我们可以完成我们的爬取任务,为何不绅士一点?
使用场景
这套爬取方法,只适合小型爬取,给新手练练手。
全站爬取,还要考虑其他接口。
而且,搜狗微信提供的接口,缺少一些更加振奋人心的参数。比如:文章阅读量,文章赞赏量等等。
最后
欢迎关注我的公众号 爬虫小栈。