Scrapy爬取知乎数据小试

 

啊啊啊,没时间写啦,以后有时间再写吧!

。。。发现今天是周五,不熄灯。。。

前两周一直在忙毕设的事情,由于某些原因毕设选择了相对简单的微信小程序,经过奋战之后一些主要的基本功能已经实现多半。

自然语言处理的一些最基本的概念已经有所了解,下面想要找点实战项目练练手。由于处理的第一步便是要获取语料,想着以后爬虫这东西肯定是要学的,于是从昨天开始学习相关视频、配置相关环境,今天看了部分,照着Demo练了练小手。

B站真是个好地方,上面有不少免费的好的视频可以看,虽然版权这方面%&@#¥!@¥……@#¥%……#@¥%##¥@

这是学习爬虫的视频链接

作者是拿轮子哥vczh作为start_user的,当时还愣了一下,可以的,会玩,想当年自己也关注过轮子哥一段时间,不过看他经常给美女们点赞、抖机灵,后来便取关了。

废话少说,言归正传:

  1. 爬虫:请求网站并提取数据的自动化程序。
  2. 爬虫的基本流程:
    • 发起请求:通过HTTP库向目标站点发起请求,即发送一个Request,情况请可以包含额外的headers等信息,等待服务器响应。
    • 获取响应内容:如果服务器能正常响应,会得到一个Response,Response的内容便是所要获取的页面内容,内容可能有HTML,Json字符串,二进制数据(如图片视频)等类型。
    • 解析内容:得到的内容可能是HTML,可以用正则表达式、网页解析库进行解析,可呢是Json,可以直接转为Json对象解析,可能是二进制数据,可以做保存或者进一步的处理。
    • 保存数据:保存形式多样,可以存为文本,也可以保存至数据库,或者保存特定格式的文件。

项目所实现的是从首个著名知乎用户(本项目中为vczh)个人信息及其所有关注人、所有粉丝相关信息爬取开始、一直延伸整个关注网,并将结果数据集保存在MongoDB中。

具体来说就是先爬轮子哥的个人信息数据,然后依次爬取他的所有关注人的个人信息以及他的所有粉丝们的个人信息,这样的策略应用到每一个爬虫经过的用户上,从而实现数据的遍历抽取。单纯从Python代码的角度上讲,类似于会重复的深度优先遍历,然而具体的Scrapy引擎内部会如何调度这些Request队列就是人家内部的算法了。

视频的上传时间是18年1月11日,当时从网页中获取到的用户信息相对比较简单、集中、丰富,今天我试了又试,发现可获取到的直接信息变少了。由于只是初步尝试,所以也就按部就班的照样执行,没有做得不偿失的优化了。

Scrapy引擎的框架大致如下:

scrapy架构

我们可以看到整个引擎主要是由四部分组成:

  • Scheduler(调度器)
  • Downloader(下载器)
  • Spiders(爬虫)
  • Item Pipeline(项目 管道)

其中Spider中定义了具体的爬虫逻辑,比如我们要怎么爬,爬什么,后面跟着的s表明这通常不是一个Spider,而是通常有多个Spider,我们可以根据不同的具体需求编写对应的Spider。

Spiders之上的是一些Spider Middlewares(即爬虫中间件),有点类似于Python中函数的修饰器,可以对函数进行一些增强和拓展,同样的,我们可以通过这些Spider中间件来丰富和拓展我们编写的小Spider,让它们表现的更给力一些,同时这也可以简化我们的编写逻辑,因为我们只需要在之上套些中间件就可以了,套什么中间件视具体需求而定,比如说冬天穿大衣、夏天穿衬衫等。

当Spider生成好之后,引擎会开始执行这个Spider的内部逻辑,如start_requests方法,和parse方法等等,具体的它会将HTTP的Request请求交给Downloader完成,由Downloader完成从Internet上下载资源数据的具体任务,而Downloader也可以有自己的中间件也就是Downloader Middlewares,可以对Downloader进行改装,增强。

Downloader完成下载后,Scrapy引擎会将Downloader的成果Responses交给之前的Spider,执行它的解析方法。之后视具体情况,Spider可能会爬取更多的数据,相应的会产生更多的Request请求,或者将Response中的数据进行处理,处理为Item,然后转交给Item Pipeline做最后的数据处理工作。

因为爬虫一般不是一个一个的爬,而是通常成百上千乃至上万的爬,Scheduler的主要作用类似于CPU的处理器对这些请求做一个规划调度,先做哪个,后做哪个等等。

Item Pipline 中,Item可以视为一个数据对象的容器,而Pipline则类似Unix系统中的管道,或者更通俗点,就像流水线的的工人,从网上获取原材料之后,Spider这个工人进行加工处理,之后这些Pipline们做做类似贴标签的工作,最终提交正式的产品。

因为我们爬虫的编写都是具有针对性的,知己知彼百战不殆嘛。所以首先要分析知乎相关的数据流通状况:

scrapy-user

以上是轮子哥的知乎页面。

首先必须要按F12打开开发者工具,查看Network页面。

对于某个用户的具体信息,如关注人或粉丝列表中的,我们只需要将鼠标请放在某个人的图像上,知乎就会通过Ajax去请求这个人的数据,以Json对象的格式返回给浏览器,同样的,当我们查看关注人列表和粉丝列表时,知乎也是通过Ajax请求返回这些数据对象的,具体的如以下几张图片:

scrapy-info

这是轮子哥关注的某个人的信息,观看图右侧我们发现这个人相关信息就在这个Json对象中。而如果要获取到这个Response体中的Json对象,我们只要执行最上面的网络请求就可以了:

scrapy-request-user

还有类似的关注人列表和粉丝列表也都是大概类似的情况,不过情况的url中的参数和内容稍有不同罢了。

关注人列表:

scrapy-followees

相关请求:

scrapy-request-followees

粉丝列表:

scrapy-followers

相关请求:

scrapy-request-followers

其实爬虫这个东西的基本原理很简单,就是执行HTTP请求,处理响应的数据,将这个过程重复化自动化而已。

而基于前面我们所提到的爬取信息的相关策略,我们要做的就是爬轮子哥的数据,然后请求两个列表中其他人的数据,并拓展开来。

整个项目的结构如下:

project-structure

这里我们需要编写的是zhihu.py、items.py、piplines.py、settings.py

在settings.py中我们进行了请求头、Item Pipline中间件和MongoDB相关的配置。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

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 6.3; W…) Gecko/20100101 Firefox/57.0',
'authorization':' oauth c3cef7c66a1843f8b3a9e6a1e3160e20'
}

ITEM_PIPELINES = {
'zhihuUser.pipelines.MongoPipeline': 300,
}

MONGO_URI = 'localhost'
MONGO_DATABASE = 'zhihu'

我们针对Json对象的内容编写的ZhihuUserItem对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

from scrapy import Item,Field

class ZhihuUserItem(Item):
# define the fields for your item here like:
# name = scrapy.Field()
id = Field()
is_followed = Field()
avatar_url_template = Field()
user_type = Field()
answer_count = Field()
is_following = Field()
url = Field()
url_token = Field()
allow_message = Field()
articles_count = Field()
is_blocking = Field()
name = Field()
headline = Field()
badge = Field()
is_advertiser = Field()
avatar_url = Field()
is_org = Field()
gender = Field()
follower_count = Field()
employments = Field()
type = Field()

因为要将数据存储到MongoDB中,所以要进行MongoDB的Item Pipline中间件的编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

import pymongo

class MongoPipeline(object):

collection_name = 'user_info'

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(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE')
)

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[self.collection_name].insert_one(dict(item))
# self.db[self.collection_name].update({'url_token': item['url_token']},{'$set': item},True)

最后是zhihu.py 中 ZhihuSpider的编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

import json

import scrapy
from scrapy import Request

from zhihuUser.items import ZhihuUserItem


class ZhihuSpider(scrapy.Spider):
name = 'zhihu'
allowed_domains = ['www.zhihu.com']
start_urls = ['http://www.zhihu.com/']

start_user = 'excited-vczh'

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'

followees_url = 'https://www.zhihu.com/api/v4/members/{user}/followees?include={include}&offset={offset}&limit={limit}'
followees_query = 'data[*].answer_count,articles_count,gender,follower_count,is_followed,is_following,badge[?(type=best_answerer)].topics'

followers_url = 'https://www.zhihu.com/api/v4/members/{user}/followers?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):
yield Request(self.user_url.format(user=self.start_user,include=self.user_query),callback=self.parse_user)
# yield Request(self.followees_url.format(user=self.start_user,include=self.followees_query,offset=0,limit=20),callback=self.parse_followees)

def parse_user(self, response):
result = json.loads(response.text)
item = ZhihuUserItem()
for field in item.fields:
if field in result.keys():
item[field] = result.get(field)
yield item
yield Request(self.followees_url.format(user=result.get('url_token'), include=self.followees_query, offset=0, limit=20),
callback=self.parse_followees)
yield Request(
self.followers_url.format(user=result.get('url_token'), include=self.followers_query, offset=0, limit=20),
callback=self.parse_followees)
# print(json.loads(response.text))

def parse_followees(self, response):
result = json.loads(response.text)
if 'data' in result.keys():
for result in result.get('data'):
yield Request(self.user_url.format(user=result.get('url_token'),include=self.user_query),callback=self.parse_user)
if 'paging' in result.keys() and result.get('paging').get('is_end') == False:
next_page = result.get('paging').get('next')
yield Request(next_page,callback=self.parse_followees)

def parse_followers(self, response):
result = json.loads(response.text)
if 'data' in result.keys():
for result in result.get('data'):
yield Request(self.user_url.format(user=result.get('url_token'),include=self.user_query),callback=self.parse_user)
if 'paging' in result.keys() and result.get('paging').get('is_end') == False:
next_page = result.get('paging').get('next')
yield Request(next_page,callback=self.parse_followers)

def parse(self, response):
pass

在终端下执行scrapy crawl zhihu后,爬虫便会开始启动,由于这个工程一直爬一直爬,所以让它爬一会做个样子就行了,通过Ctrl+C停止当前任务,随后我们可以通过可视化工具查看到存入MongoDB中的数据:

scrapy-mongo-data

大功告成!虽然超级简单。。。