一:爬取思路html
首先咱们应该找到一个帐号,这个帐号被关注的人和关注的人都相对比较多的,就是下图中金字塔顶端的人,而后经过爬取这个帐号的信息后,再爬取他关注的人和被关注的人的帐号信息,而后爬取被关注人的帐号信息和被关注信息的关注列表,爬取这些用户的信息,经过这种递归的方式从而爬取整个知乎的全部的帐户信息。整个过程经过下面两个图表示:git
二:爬虫过程分析github
这里咱们找的帐号地址是:https://www.zhihu.com/people/excited-vczh/answersmongodb
下图是大V的主要信息:数据库
而后咱们获取他关注的人和关注他的人的信息:json
这里咱们须要经过抓包分析若是获取这些列表的信息以及用户的我的信息内容
当咱们查看他关注人的列表的时候咱们能够看到他请求了以下图中的地址,而且咱们能够看到返回去的结果是一个json数据,而这里就存着一页关乎的用户信息。api
上面虽然能够获取单个用户的我的信息,可是不是特别完整,这个时候咱们获取一我的的完整信息地址是当咱们将鼠标放到用户名字上面的时候,能够看到发送了一个请求:app
咱们能够看这个地址的返回结果能够知道,这个地址请求获取的是用户的详细信息:dom
经过上面的分析咱们知道了如下两个地址:scrapy
1关注列表:https://www.zhihu.com/api/v4/members/excited-vczh/followees?include=data%5B*%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F(type%3Dbest_answerer)%5D.topics&offset=0&limit=20
二、详情信息:https://www.zhihu.com/api/v4/members/nan-xia-95-92?include=allow_message%2Cis_followed%2Cis_following%2Cis_org%2Cis_blocking%2Cemployments%2Canswer_count%2Cfollower_count%2Carticles_count%2Cgender%2Cbadge%5B%3F(type%3Dbest_answerer)%5D.topics
这里咱们能够从请求的这两个地址里发现一个问题,关于用户信息里的url_token其实就是获取单个用户详细信息的一个凭证也是请求的一个重要参数,而且当咱们点开关注人的的连接时发现请求的地址的惟一标识也是这个url_token。
三:建立项目实战
经过命令建立项目
scrapy startproject zhihu_user
cd zhihu_user
scrapy genspider zhihu zhihu.com
建立好后用pycharm打开:
更改settings文件:
# 是否遵循爬取规则,咱们改为False
ROBOTSTXT_OBEY = False
# 添加请求头信息,由于知乎默认检测请求头的
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
'authorization': 'oauth c3cef7c66a1843f8b3a9e6a1e3160e20',
}
四:代码实现
(1):items中的代码主要是咱们要爬取的字段的定义
import scrapy class UserItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() id = scrapy.Field() name = scrapy.Field() allow_message = scrapy.Field() answer_count = scrapy.Field() articles_count = scrapy.Field() avatar_url = scrapy.Field() avatar_url_template = scrapy.Field() badge = scrapy.Field() employments = scrapy.Field() follower_count = scrapy.Field() gender = scrapy.Field() headline = scrapy.Field() is_advertiser = scrapy.Field() is_blocking = scrapy.Field() is_followed = scrapy.Field() is_following = scrapy.Field() is_org = scrapy.Field() type = scrapy.Field() url = scrapy.Field() url_token = scrapy.Field() user_type = scrapy.Field()
(2):spiders中的主要代码
# -*- coding: utf-8 -*- import json import scrapy from scrapy_zhihuuser.items import UserItem class ZhihuSpider(scrapy.Spider): name = 'zhihu' allowed_domains = ['zhihu.com'] start_urls = ['http://zhihu.com/'] # 起始的大V帐号 start_user = 'excited-vczh' # 这里把查询的参数单独存储为user_query,user_url存储的为查询用户信息的url地址 user_url = 'https://www.zhihu.com/api/v4/members/{user}?include={include}' user_query = 'allow_message,is_followed,is_following,is_org,is_blocking,employments,answer_count,follower_count,articles_count,gender,badge[?(type=best_answerer)].topics' # #follows_url存储的为关注列表的url地址,fllows_query存储的为查询参数。这里涉及到offset和limit是关于翻页的参数,0,20表示第一页 follows_url = 'https://www.zhihu.com/api/v4/members/{user}/followers?include={include}&offset={offset}&limit={limit}' follows_query = 'data[*].answer_count,articles_count,gender,follower_count,is_followed,is_following,badge[?(type=best_answerer)].topics' # #followers_url是获取粉丝列表信息的url地址,followers_query存储的为查询参数。 followers_url = 'https://www.zhihu.com/api/v4/members/{user}/followees?include={include}&offset={offset}&limit={limit}' followers_query = 'data[*].answer_count,articles_count,gender,follower_count,is_followed,is_following,badge[?(type=best_answerer)].topics' # 第一次访问的方法重写 def start_requests(self): """ 这里重写了start_requests方法,分别请求了用户查询的url和关注列表的查询以及粉丝列表信息查询 :return: """ yield scrapy.Request(self.user_url.format(user=self.start_user, include=self.user_query), callback=self.parse_user) yield scrapy.Request( self.follows_url.format(user=self.start_user, include=self.follows_query, offset=0, limit=20), callback=self.parse_follows) yield scrapy.Request( self.follows_url.format(user=self.start_user, include=self.followers_query, offset=0, limit=20), callback=self.parse_followers) def parse_user(self, response): """ 由于返回的是json格式的数据,因此这里直接经过json.loads获取结果 :param response: :return: """ result = json.loads(response.text) item = UserItem() # 这里循环判断获取的字段是否在本身定义的字段中,而后进行赋值 for field in item.fields: if field in result.keys(): item[field] = result.get(field) # 这里在返回item的同时返回Request请求,继续递归拿关注用户信息的用户获取他们的关注列表 yield item yield scrapy.Request( self.follows_url.format(user=result.get("url_token"), include=self.follows_query, offset=0, limit=20), callback=self.parse_follows) yield scrapy.Request( self.followers_url.format(user=result.get("url_token"), include=self.followers_query, offset=0, limit=20), callback=self.parse_followers) def parse_follows(self, response): # 用户关注列表的解析,这里返回的也是json数据 这里有两个字段data和page,其中page是分页信息 results = json.loads(response.text) if 'data' in results.keys(): for result in results.get('data'): yield scrapy.Request(self.user_url.format(user=result.get('url_token'), include=self.user_query), self.parse_user) # 这里判断page是否存在而且判断page里的参数is_end判断是否为False,若是为False表示不是最后一页,不然则是最后一页 if 'paging' in results.keys() and results.get('paging').get('is_end') == False: next_page = results.get('paging').get('next') # 获取下一页的地址而后经过yield继续返回Request请求,继续请求本身再次获取下页中的信息 yield scrapy.Request(next_page, self.parse_follows) def parse_followers(self, response): """ 这里其实和关乎列表的处理方法是同样的 用户粉丝列表的解析,这里返回的也是json数据 这里有两个字段data和page,其中page是分页信息 :param response: :return: """ results = json.loads(response.text) if 'data' in results.keys(): for result in results.get('data'): yield scrapy.Request(self.user_url.format(user=result.get('url_token'), include=self.user_query), self.parse_user) if 'paging' in results.keys() and results.get('paging').get('is_end') == False: next_page = results.get('paging').get('next') yield scrapy.Request(next_page, self.parse_followers)
关于上面爬虫的简单描述:
1. 当重写start_requests,一会有三个yield,分别的回调函数调用了parse_user,parse_follows,parse_followers,这是第一次会分别获取咱们所选取的大V的信息以及关注列表信息和粉丝列表信息
2. 而parse分别会再次回调parse_follows和parse_followers信息,分别递归获取每一个用户的关注列表信息和分析列表信息
3. parse_follows获取关注列表里的每一个用户的信息回调了parse_user,并进行翻页获取回调了本身parse_follows
4. parse_followers获取粉丝列表里的每一个用户的信息回调了parse_user,并进行翻页获取回调了本身parse_followers
(3):关于数据存储到mongodb
更改pipeline代码:
import pymongo class MongoPipeline(object): collection_name = 'scrapy_items' def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls(
# 在settings中定义数据库相关的操做 mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE', 'items') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db['user'].update({'url_token': item['url_token']}, {'$set': item}, True) # 更新去重 # self.db[self.collection_name].insert_one(dict(item)) return item
项目代码--》GitHub地址