搜狗微信文章爬取(下)

时间:2024-03-21 15:57:22

GitHub:

https://github.com/utopianist/SougouWeixin


前言

上节我们爬完了所有索引页,这节我们开始爬取公众号文章。

搜狗微信文章爬取(下)

要知道,我们之前爬的是 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&timestamp=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()

至此,爬取搜狗微信文章就算完成。我们就有超多文章可以看了,

搜狗微信文章爬取(下)

然而,并没有太大卵用。

总结

代码性能

在爬取速率上,我并没有采取一些加速的方法,比如异步协程等等。

因为我害怕触发更加强大的反爬。

毕竟爬和反爬就是“道高一尺,魔高一丈”的事情,既然我们可以完成我们的爬取任务,为何不绅士一点?

使用场景

这套爬取方法,只适合小型爬取,给新手练练手。

全站爬取,还要考虑其他接口。

而且,搜狗微信提供的接口,缺少一些更加振奋人心的参数。比如:文章阅读量,文章赞赏量等等。

最后

欢迎关注我的公众号 爬虫小栈

搜狗微信文章爬取(下)