使用Scrapy爬取知乎用户信息

2017-06-04  本文已影响266人  朱晓飞

本文记录了关于知乎用户信息的模块化抓取,使用到了Scrapy这个开源项目,对其不熟悉的同学建议提前了解

知乎是现在十分活跃的社区,上面有关于人生、智慧、职业、技术等等的一系列的高质量的问答和专栏文章,虽然总是有有一些负面,片面的观点,但是不得不承认这是一个积极的、开放的社区

而作为学习者,需要做的总是抱着开放的心态,去其糟粕而取之精华,知乎虽是虚拟,但更像现实。

起初我的思路是先使用Scrapy模拟登录上知乎,获得server分配的cookie,借鉴了Github上这个项目fuck-login,它的源码使用的requests模拟登陆知乎

requests session在第一次登陆成功后,获得cookie设置之后,requests session可以帮助我们管理cookie,这减少了很多的底层劳动。在下次爬取可以使用session来进行get/post请求

当然你也可以使用浏览器先登陆上知乎,直接将cookie持久化到本地,之后再在请求的头中带上cookie

原理

模拟登陆

要将这个项目集成到Scrapy当中,需要使用ScrapyRequest重写下,实现模拟登录

要想登陆上知乎,需要在请求头中修改User-Agent,并向login页面POST一些数据,注意这里重写了需要是用账号为手机号登陆,至于email登陆可以借鉴原项目

不知道要带哪些东西就需要使用chrome开发者工具先看下正常请求会带哪些东西,之后再COSPLAY,而fuck-login就帮助我们做好了这些事情

headers = {
    "Host": "www.zhihu.com",
    "Referer": "https://www.zhihu.com/",
    'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0'
}

postdata = {
    '_xsrf': '',
    'password': 'xxxxxx',
    'phone_num': 'xxxxxx',
    'captcha': ''
}

对于验证码的操作,也用很多种方式,有编码实现,比如tesseract-ocr (google开源) 不推荐,也有在线打码的平台,准确率尚可,成本较低,比如云打码,还有其他人工打码,准确率最高,但是成本也是最高

这里遵循fuck-login的做法,将登陆页面的验证码图片链接下载到本地,直接手动输入

源码如下:

# -*- coding: utf-8 -*-
import scrapy
import re,os,json
import time
try:
    from PIL import Image
except:
    pass

class ZhihuSpider(scrapy.Spider):
    name = "zhihu"
    allowed_domains = ["www.zhihu.com"]
    start_urls = ['https://www.zhihu.com/people/zhu-xiao-fei-47-24/following']

    headers = {
        "Host": "www.zhihu.com",
        "Referer": "https://www.zhihu.com/",
        'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0'
    }

    postdata = {
        '_xsrf': '',
        'password': 'zhxfei..192',
        'phone_num': '15852937839',
        'captcha': ''
    }

    def parse(self, response):
        with open('index.html','w') as f:
            f.write(response.text)
        print('over')

    def start_requests(self):
        return [scrapy.Request('https://www.zhihu.com/#login',
                          headers=self.headers,callback=self.get_xsrf)]

    def get_xsrf(self,response):
        response_text = response.text
        mathch_obj = re.match('.*name="_xsrf" value="(.*?)"',response_text,re.DOTALL)
        if mathch_obj:
            self.postdata['_xsrf'] =  mathch_obj.group(1)
            t = str(int(time.time() * 1000))
            captcha_url = 'https://www.zhihu.com/captcha.gif?r='+t+"&type=login"
            return [scrapy.Request(
                    captcha_url,headers=self.headers,callback=self.get_captcha)]

    def get_captcha(self,response):
        with open('captcha.jpg', 'wb') as f:
            f.write(response.body)
            f.close()
        try:
            im = Image.open('captcha.jpg')
            im.show()
            #im.close()
        except:
            print('find captcha by your self')
        self.postdata['captcha'] = input("please input the captcha\n>").strip()
        if self.postdata['_xsrf'] and self.postdata['captcha']:
            post_url = 'https://www.zhihu.com/login/phone_num'
            return [scrapy.FormRequest(
                url=post_url,
                formdata=self.postdata,
                headers=self.headers,
                callback=self.check_login
            )]

    def check_login(self,response):
        json_text = json.loads(response.text)
        if 'msg' in json_text and json_text['msg'] == '登录成功':
            for url in self.start_urls:
                yield scrapy.Request(url,dont_filter=True,headers=self.headers)  #no callback , turn into parse

信息提取

思路

Scrapy获得了cookie就可以登陆上知乎了,剩下的就是爬虫逻辑和信息的提取具体实现了

具体的逻辑是从Aljun那里获得的灵感,首先从一个大V开始(比如我,哈哈哈~) 获得所以其所关注的人,之后再获得这些人的信息将一些小号给过滤掉并录入数据库,之后在从其关注的人再获得其所关注的人,再获得信息录入数据库,就这样不间断的获取下去,而Scrapy自身就遵循了深度优先的算法

观察下知乎的页面的请求流程可以发现知乎用户模块前后端是分离的,知乎后端的api看起来也和规范,前端用ajax到后端的API去拿数据,模板渲染等工作交给了react

由于每刷新一次页面都需要发起Ajax请求到后端去拿数据(为了保证数据的实时性),我们可以用开发者工具调试页面,刷新一次将http请求拿出来看下所有请求的URL,没有被缓存的请求都观察一番就教容易找出了Ajax请求的接口

首先我们先设计数据库,这里使用MySQL,我们可以根据感兴趣的可以得到的用户信息数据来设计我们的数据库,知乎提供了一个API接口来获得数据(先看看,我没有用到这个接口)

知乎开放的可以获取一个用户的具体信息的API
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24,其中url中编码一些查询参数,就可以获得用户对应的信息

https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24?include=locations%2Cemployments%2Cgender%2Ceducations%2Cbusiness%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Ccover_url%2Cfollowing_topic_count%2Cfollowing_question_count%2Cfollowing_favlists_count%2Cfollowing_columns_count%2Cavatar_hue%2Canswer_count%2Carticles_count%2Cpins_count%2Cquestion_count%2Ccolumns_count%2Ccommercial_question_count%2Cfavorite_count%2Cfavorited_count%2Clogs_count%2Cmarked_answers_count%2Cmarked_answers_text%2Cmessage_thread_token%2Caccount_status%2Cis_active%2Cis_force_renamed%2Cis_bind_sina%2Csina_weibo_url%2Csina_weibo_name%2Cshow_sina_weibo%2Cis_blocking%2Cis_blocked%2Cis_following%2Cis_followed%2Cmutual_followees_count%2Cvote_to_count%2Cvote_from_count%2Cthank_to_count%2Cthank_from_count%2Cthanked_count%2Cdescription%2Chosted_live_count%2Cparticipated_live_count%2Callow_message%2Cindustry_category%2Corg_name%2Corg_homepage%2Cbadge[%3F(type%3Dbest_answerer)].topics

向这个接口请求发起一个GET请求,就可以获得后台发送来的JSON数据,这个信息是比较完善的,当我们知道可以获取哪些信息,找出自己关注的信息,就可以设计我们的数据库了,

这里需要注意的是,显然这个数据太庞大了,我们应该根据我们的需求编码不同的参数进去从而获得我们想要的数据,从而减少请求的JSON数据的大小,节省带宽

如我们设计的Item是以下结构(和mysql中的数据表的列相互对应)

class ZhihuUserItem(Item):
    name = scrapy.Field()
    id = scrapy.Field()
    url_token = scrapy.Field()
    headline = scrapy.Field()
    answer_count = scrapy.Field()
    articles_count = scrapy.Field()
    gender = scrapy.Field()
    avatar_url = scrapy.Field()
    user_type = scrapy.Field()
    following_count = scrapy.Field()
    follower_count = scrapy.Field()
    thxd_count = scrapy.Field()
    agreed_count = scrapy.Field()
    collected_count = scrapy.Field()
    badge = scrapy.Field()
    craw_time = scrapy.Field()

而我想获得这样的JSON数据:

{
  "following_count": 35,
  "user_type": "people",
  "id": "113d0e23a9ada1a61faf0272b4acf6c4",
  "favorited_count": 38,
  "voteup_count": 31,
  "headline": "学生",
  "url_token": "zhu-xiao-fei-47-24",
  "follower_count": 22,
  "avatar_url_template": "https://pic2.zhimg.com/20108b43c7b928229ba5cfafccca1235_{size}.jpg",
  "name": "朱晓飞",
  "thanked_count": 17,
  "gender": 1,
  "articles_count": 0,
  "badge": [ ],
  "answer_count": 32,
}

可以编码这样的请求进去

https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24?include=gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge[%3F(type%3Dbest_answerer)].topics

同理

知乎后台开放了获取一个用户关注者的API
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees,显然这个接口和用户相关如zhu-xiao-fei-47-24,这是用户的一个属性url_token,我们可以利用用户的url_token来拼接出获得其关注者的url

由于查询的HTTP MethodGet,查询的参数是编码到url中,我们也可以在urlencode一些请求的参数进去,来获得对应的数据,如

https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data[*].answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge[%3F(type%3Dbest_answerer)].topics&offset=20&limit=20

向这个请求发起一个GET请求,就可以获得后台发送来的Json数据,截取部分实例如下:

{

    "paging": {
        "is_end": true,
        "totals": 35,
        "previous": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=0",
        "is_start": false,
        "next": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=40"
    },
    "data": [
        {
            "is_followed": false,
            "avatar_url_template": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_{size}.jpg",
            "user_type": "people",
            "answer_count": 391,
            "is_following": true,
            "url": "http://www.zhihu.com/api/v4/people/29d476a5746f5dd5ae8a296354e817de",
            "type": "people",
            "url_token": "chexiaopang",
            "id": "29d476a5746f5dd5ae8a296354e817de",
            "articles_count": 28,
            "name": "车小胖",
            "headline": "网络主治大夫,专治疑难杂症",
            "gender": 1,
            "is_advertiser": false,
            "avatar_url": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_is.jpg",
            "is_org": false,
            "follower_count": 25580,
            "badge": [
                {
                    "topics": [
                        {
                            "url": "http://www.zhihu.com/api/v4/topics/19572894",
                            "avatar_url": "https://pic3.zhimg.com/e0bd139b2_is.jpg",
                            "name": "计算机网络",
                            "introduction": "计算机网络( <a href=\"http://www.wikiwand.com/en/Computer_Networks\" data-editable=\"true\" data-title=\"Computer Networks\">Computer Networks</a> )指将地理位置不同的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。",
                            "type": "topic",
                            "excerpt": "计算机网络( Computer Networks )指将地理位置不同的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。",
                            "id": "19572894"
                        }
                    ],
                    "type": "best_answerer",
                    "description": "优秀回答者"
                }
            ]
        },
        //.... 还有19个用户的数据
    ]
}

可以看到,在这个API里也可以返回关注用户的信息,也就是每一个data字段里面的信息。这就是我们要的接口了!

我们可以根据我们的需求构造出URL去获取我们想要的对应的数据。这个接口可以加三个参数

第一个include就是我们可以请求到的用户的信息,第二个offset是偏移量表征当前返回的第一个记录相对第一个following person的数量,第三个limit是返回限制数量,后面两个貌似起不到控制作用,所以可以无视,但是Spider对于一个没有提取过following person的时候,需要将offset设置为0。

而第一个参数include就是关注人的信息,我们可以将用户的属性如感谢数使用thanked_Count%2C拼接起来:所以根据上面的需求,我们可以这么编码

https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data[*].gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge[%3F(type%3Dbest_answerer)].topics&offset=20&limit=20

请求这个接口,就可以获得我们数据库所需要的信息,并且可以不传输大量的数据,如下:

{

    "paging": {
        "is_end": true,
        "totals": 35,
        "previous": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=0",
        "is_start": false,
        "next": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=40"
    },
    "data": [
        {
            "avatar_url_template": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_{size}.jpg",
            "following_count": 118,
            "user_type": "people",
            "answer_count": 391,
            "headline": "网络主治大夫,专治疑难杂症",
            "url_token": "chexiaopang",
            "id": "29d476a5746f5dd5ae8a296354e817de",
            "favorite_count": 0,
            "articles_count": 28,
            "type": "people",
            "name": "车小胖",
            "url": "http://www.zhihu.com/api/v4/people/29d476a5746f5dd5ae8a296354e817de",
            "gender": 1,
            "favorited_count": 27051,
            "is_advertiser": false,
            "avatar_url": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_is.jpg",
            "is_org": false,
            "thanked_count": 7161,
            "follower_count": 25588,
            "voteup_count": 30449,
            "badge": [
                {
                    "topics": [
                        {
                            "introduction": "计算机网络( <a href=\"http://www.wikiwand.com/en/Computer_Networks\" data-editable=\"true\" data-title=\"Computer Networks\">Computer Networks</a> )指将地理位置不同的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。",
                            "avatar_url": "https://pic3.zhimg.com/e0bd139b2_is.jpg",
                            "name": "计算机网络",
                            "url": "http://www.zhihu.com/api/v4/topics/19572894",
                            "type": "topic",
                            "excerpt": "计算机网络( Computer Networks )指将地理位置不同的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。",
                            "id": "19572894"
                        }
                    ],
                    "type": "best_answerer",
                    "description": "优秀回答者"
                }
            ]
        },
        //.... 还有19个用户的数据
    ]
}

注意
在我们获取我们想要的数据的时候,我们的爬虫应该遵守一个原则就是:

尽可能减少我们的HTTP次数

在我们调整请求的URL之后,相当于一个HTTP请求,就可以获得20item,而不是一个请求获得url_token,每一个用户的信息再需要一次http request获得,光这项的修改相当于提升了爬虫20倍的性能,当然说的有些夸张。但是,爬虫的瓶颈逐渐不是信息的获取,可能性能会损耗在在我们的数据库的写入

实现

此时,即可在模拟登陆的基础上,完善我们的spider,主要增加parse这个实例方法

following_api = "https://www.zhihu.com/api/v4/members/{}/followees?include=data[*].gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge[%3F(type%3Dbest_answerer)].topics&offset=20&limit=20"


class ZhihuSpider(scrapy.Spider):
    start_urls = [following_api.format('teng-xun-ke-ji')]

    '''
    模拟登陆的代码
    '''

    def parse(self, response):
        jsonresponse = json.loads(response.body_as_unicode())

        if not jsonresponse['paging']['is_end']:
            yield scrapy.Request(url=jsonresponse['paging']['next'])

        if jsonresponse['data']:
            for data in jsonresponse['data']:
                url_token = data.get('url_token')
                if url_token:
                    yield scrapy.Request(url=following_api.format(url_token))

                    agreed_count = data['voteup_count']
                    thxd_count = data['thanked_count']
                    collected_count = data['favorited_count']
                    if thxd_count or collected_count:
                        item_loader = ZhihuUserItemLoader(item=ZhihuUserItem(), response=response)
                        item_loader.add_value('name',data['name'])
                        item_loader.add_value('id',data['id'])
                        item_loader.add_value('url_token',data['url_token'])
                        item_loader.add_value('headline',data['headline']
                                                            if data['headline'] else "无")
                        item_loader.add_value('answer_count',data['answer_count'])
                        item_loader.add_value('articles_count',data['articles_count'])
                        item_loader.add_value('gender',data['gender']
                                                            if data['gender'] else 0)
                        item_loader.add_value('avatar_url',data['avatar_url_template'].format(size='xl'))
                        item_loader.add_value('user_type',data['user_type'])
                        item_loader.add_value('badge',','.join([badge.get('description') for badge in data['badge']])
                                                            if data.get('badge') else "无")
                        item_loader.add_value('follower_count',data['follower_count'])
                        item_loader.add_value('following_count',data['following_count'])
                        item_loader.add_value('agreed_count',agreed_count)
                        item_loader.add_value('thxd_count',thxd_count)
                        item_loader.add_value('collected_count',collected_count)
                        item_loader.add_value('craw_time',datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                        zhihu_item = item_loader.load_item()
                        yield zhihu_item

数据入库
获得到数据,即可将item的信息就可以插入到MySQL中,可以添加一个pipeline

class MysqlTwsitedPipeline(object):
    def __init__(self,dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_settings(cls,settings):
        dbpara=dict(
            host=settings['MYSQL_HOST'],
            db=settings['MYSQL_NAME'],
            user=settings['MYSQL_USER'],
            passwd=settings['MYSQL_PASS'],
            charset='utf8',
            cursorclass=MySQLdb.cursors.DictCursor,
            use_unicode=True
        )
        dbpool = adbapi.ConnectionPool("MySQLdb",**dbpara)
        return cls(dbpool)

    def process_item(self, item, spider):
        query = self.dbpool.runInteraction(self.do_insert,item)
        query.addErrback(self.handle_error)
        return item

    def handle_error(self,failure):
        print(failure)

    def do_insert(self,cursor,item):
        insert_sql = item.get_insert_sql()
        cursor.execute(insert_sql)

完整的item:

class ZhihuUserItem(Item):
    name = scrapy.Field()
    id = scrapy.Field()
    url_token = scrapy.Field()
    headline = scrapy.Field()
    answer_count = scrapy.Field()
    articles_count = scrapy.Field()
    gender = scrapy.Field()
    avatar_url = scrapy.Field()
    user_type = scrapy.Field()
    following_count = scrapy.Field()
    follower_count = scrapy.Field()
    thxd_count = scrapy.Field()
    agreed_count = scrapy.Field()
    collected_count = scrapy.Field()
    badge = scrapy.Field()
    craw_time = scrapy.Field()

    def get_insert_sql(self):
        insert_sql = '''
            replace into zhihu_user1 values('{}','{}','{}','{}',{},{},{},'{}','{}',{},{},{},{},{},'{}','{}')
            '''.format(self['name'], self['id'], self['url_token'], self['headline'], self['answer_count'],
                       self['articles_count'], self['gender'], self['avatar_url'], self['user_type'],
                       self['following_count'], self['follower_count'], self['thxd_count'],
                       self['agreed_count'], self['collected_count'], self['badge'], self['craw_time'])

        return insert_sql

piplinehandle_error(异常处理处)函数内打上断点,使用DEBUG调试程序,观察到有数据入库即可

然而运行我们的project,没抓到几百个用户数据,就会出现http 429甚至http 403的情况

http 429解决办法
http 429意思请求的速率太快,显然知乎后台开放的API会做一些调用限制,比如对IP、调用的用户等。

Scrapy目前我没有想到行知有效的方式,我认为最为方便的就是设置下载延时,在setting.py文件中设置DOWNLOAD_DELAY这个变量

并随机切换代理和User-Agent,编写Scrapymiddleware,如下:

class ProxyMiddleware(object):
    # overwrite process request

    def process_request(self, request, spider):
        # Set the location of the proxy

        proxy_ip_list = [
            'http://127.0.0.1:9999',
            'http://120.xxx.x.x:xxx',
            #....
        ]

        user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
            "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
            "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
            "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
            "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
            "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
            "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
            "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
            "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3"
        ]

        proxy_ip = random.choice(proxy_ip_list)
        ua = random.choice(user_agent_list)
        request.headers.setdefault('User-Agent', ua)

        request.meta['proxy'] = proxy_ip
        print('the Current ip address is', proxy_ip)

setting中设置使用上编写好的下载中间件

至此我们的爬虫大部分已经完成了。再次运行我们的爬虫,当爬数据到一定数量(也没多少),开始报http 403,拷贝对应的请求url到浏览器中,发现需要重新填写验证码。没一会,知乎便对我的id进行了限制,手机客户端也无法正常浏览

我猜测,知乎可能根据cookie对触发请求阈值的用户识别后禁止...所以需要在setting.py中设置COOKIES_ENABLES=False

使用Oauth匿名爬取

前几天看到静谧的做法

其根本就不要模拟登陆,但是为了可以访问到信息,需要探测出Oauth的值加入到header

这个Oauth相当一个令牌,我对其目前还不太了解,先不做阐述。

需要注意的是,我们在上面的ProxyMiddleware中重写了header,所以需要在ProxyMiddleware里面加上这个header

request.headers.setdefault('authorization',
                            'oauth c3cef7c66a1843f8b3a9e6a1e3160e20')

于是乎,我们只需要关注优质、稳定的代理,设置好下载延时,就可以进行爬取了

当我们以匿名的形式,也就没有之前模拟登陆的许多限制,但是也是需要注意设置延时和代理、随机User-Agent

在单机设置为DOWNLOAD_DELAY = 0.4,设置两个代理的情况下,每小时大概能抓到2W+的用户数据,以这种形式我们的爬虫的速率已经算是比较高了。

上一篇下一篇

猜你喜欢

热点阅读