使用代理抓取微信文章

前言


我在上一篇中介绍了requests.get()的请求过程(点我直达),今天我将利用这个原因来实现对微信公众号文章信息的抓取

在PC上我们如要要抓取微信公众号文章可以使用搜狗的搜索引擎,它会显示最新的文章,但是有几个问题

  • 搜狗微信站点的反爬比较严格,如果只是用本地IP(单IP)去抓取的话肯定是不行的,这个时候我们需要用到前面的代理池(通过可用随机代理去绕过反爬机制)
  • 如果要抓取某一个主题(比如在搜索数据挖掘)的所有记录的话,需要先登录(也就是你的请求头headers中要有登陆之后服务器返回的cookies),未登录只可以查看10页,登录之后可以查看100页


它由哪几部分构成?


程序流程图.jpg


01 调度器


检测队列中的请求对象,只要队列不为空,调用pop方法获取一个请求对象,然后发起请求,判断返回的响应是数据还是请求,如果是请求,就加到请求队列中,如果是数据就存到数据库中


02 构建一个WeixinRequest类


这里我们为什么要自己实现一个WeixinRequest对象呢?直接用requests中的Request对象不行么?


我们这么做的原因是:我们需要在请求对象中增加一个回调参数callback(指定在获取响应之后由哪个函数去进行解析),单纯使用Request对象不能满足这个需求,所以我们要继承这个类,然后添加一些新的东西

可能有人就会问了,为什么要增加这个callback参数,在获取响应之后,我直接把解析函数写在一个模块里然后引入这个模块,再调用相应的方法去解析不就好了吗?如果你在很多文件中引用了这个模块内的解析函数,在某种情况下你修改了解析函数的函数名,在不加callback参数的情况下,你就势必要去修改引用这些解析函数的文件,这将变得很麻烦。一旦加了callback之后,你只需在构建请求对象的参数中修改一下callback的值即可,这使得程序更加灵活


03 构建一个请求队列


这个请求队列需要实现三个功能

  • 增加一个请求对象(队列中只能存字符串,不能直接存对象,所以我们在存的时候需要对其序列化)
  • 获取一个请求对象(在从队列获取一个对象的时候我们需要对其反序列化,获得一个真正的请求对象)
  • 判断队列是否为空


04 数据存储


将数据存储到MySQL中,包含一下字段

  • 文章发布时间
  • 文章标题
  • 公众号名称
  • 作者
  • 文章内容


如何实现


# WeixinRequest的实现
class WeixinReq(Request):
    def __init__(self,url,callback,headers=None,timeout=15,\
            method='get',need_proxy=False,fail_time=0):
        Request.__init__(self,method=method,url=url,headers=headers)
        self.timeout = timeout
        self.fail_time = fail_time
        self.need_proxy = need_proxy
        self.callback = callback


# MySQL存储
class MySQL(object):
    def __init__(self):
        self.db = pymysql.connect(MYSQL_HOST,MYSQL_USER,MYSQL_PASSWD,\
                            MYSQL_DB,charset="utf8",port=MYSQL_PORT)
        self.cursor = self.db.cursor()

    def insert(self,table,data):
        keys = ','.join(data.keys())
        values = ','.join(['%s'] * len(data))
        sql_query = "insert into {} ({}) values ({}) "\
                        .format(table,keys,values)
        try:
            self.cursor.execute(sql_query,tuple(data.values()))
            flag = self.db.commit()
            if flag != 0:
                print("insert successfully")
        except Exception as e:
            _ = e
            print(e.args)
            self.db.rollback()
        


# 请求队列
class RedisQueue():
    def __init__(self):
        self.db = StrictRedis(host=REDIS_HOST,port=REDIS_PORT,\
                password=REDIS_PASSWD,decode_responses=False)
    
    def pop(self):
        #  这里的lpop方法表示从列表头获取一个对象
        #   队列是FIFO,我们在表尾增加元素,所以获取对象应该从表头开始
        try:
            if self.db.llen(REDIS_KEYS):
                return loads(self.db.lpop(REDIS_KEYS))
            else:
                return False
        except Exception as e:
            _ = e
            print(e)
            return False


    def add(self,request):
        if isinstance(request,WeixinReq):
            #  rpush方法表示将对象加入到列表(队列应该是FIFO,所以选择列表这个数据结构)末尾
            return self.db.rpush(REDIS_KEYS,dumps(request))
        else:
            return False
    

    def empty(self):
        return self.db.llen(REDIS_KEYS) == 0


# 调度器

class Spider(object):
    header = {
        # 列表页的请求头
    }

    detail_header = {
        # 这里写上详情页的请求头
    }


    # 这里定义几个全局变量,也叫作类变量,每个实例都能访问
    base_url = "http://weixin.sogou.com/weixin"
    keyword = '数据挖掘'
    session = Session()
    session.headers.update(header)
    queue = RedisQueue()
    mysql = MySQL()



    def parse_index(self,response):
        # 解析列表页
            


    def parse_detail(self,response):
        # 解析详情页


   # 这个是获取代理池随机代理
    def get_proxy(self):
        api_url = 'http://localhost:5000/random'
        try:
            response = requests.get(api_url)
            if response.status_code in VALID_CODE:
                ip_port = response.text
                print(ip_port)
                proxy = {
                    'http':'http://{}'.format(ip_port),
                    'https':'https://{}'.format(ip_port)
                }
                return proxy
            return None
        except Exception as e:
            _ = e
            return None

  # 这个是阿布云的付费代理
    def get_proxy_by_aby(self):
        proxies = PROXIES
        return proxies


    def request(self,req): 
        try:
            proxy = self.get_proxy_by_aby()
            if proxy:
                return self.session.send(self.session.prepare_request(req),\
                        timeout=req.timeout,proxies=proxy,allow_redirects=True)
            else:
                return  self.session.send(self.session.prepare_request(req),\
                        timeout=req.timeout)
           
        except (ConnectionError,ReadTimeout) as e:
            print(e.args)
            return False
    


    def index(self,url):
        req = WeixinReq(url,self.parse_index,headers=self.header)
        if isinstance(req,requests.Request):
            self.queue.add(req)



    def error(self,req):
        req.fail_time = req.fail_time + 1
        print("req faild time {}".format(req.fail_time))
        if req.fail_time < MAX_FAIL_TIME:
            self.queue.add(req)

    def scheduler(self):
        # 判断队列状态,然后挨个去发起请求
        # 如果返回的是一个还是一个WeixinRequest对象,就加入到请求队列
        # 如果返回一个json,就写入数据库中
                        
    
    def run(self,page=1):
        parrms = {
            'type':2,
            'query':self.keyword,
            '_sug_type_':'',
            '_sug_':'n',
            's_from':'input',
            'ie':'utf8'
        }
        for i in range(1,MAX_PAGE + 1):
            print("正在抓取第{}页".format(i))
            parrms['page']=i
            url = self.base_url + '?' + urlencode(parrms)
            self.index(url)
            self.scheduler()
            print("成功抓取第{}页".format(i))
            sleep(1)


测试是否成功


运行一下,可以看到如下结果。

运行结果.jpg

完整的程序代码请看这里:github传送门


最后



虽然搜狗微信站点可以抓取微信公众号文章,但是对于特定的公众号,只能显示最新的10篇,而且最多只能搜索100页的数据。不过微信在之前已经增加了app内搜索文章和公众号的功能,接下来可能会尝试一下用CharlsAppium实现移动端对于数据的抓取

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • 大四第六天 教招 论文 钢琴 古筝 肚子饿 热 都混在这半年里 口好干啊 人好闷
    慢Queen阅读 116评论 0 1
  • 《无问西东》作为为庆祝清华大学百年校庆的献礼影片,影片通过吴岭澜、沈光耀、陈鹏和张果果四代清华人的故事,圆满完成了...
    百毛巾阅读 945评论 0 0
  • 是偶然之间听到的盛夏,一个相反的季节,却还是莫名喜欢,有一种让人一直想听下去的冲动,于是单曲循环单曲循环好嘛...
    澄默阅读 258评论 0 0
  • 暑假培训结束,全身放松下来,在家里呆着无所事事,这样的闲日子,很难过! 转眼就过完了七月,八月又快...
    A真心待人阅读 383评论 0 0