这期的爬虫是爬取“简书”的搜索结果页,篇幅将会分为两部分来写,第一部分是爬虫部分,主要涉及搜索文章的提取和数据保存,第二部分涉及基本的数据分析和可视化,本篇文章属于爬虫篇。
爬虫源代码
首先看一下整个爬虫的源代码,每个函数的用处已经写在函数说明中,后面也会进行代码解读。
# -*- coding: utf-8 -*-
import requests
import json
from urllib.parse import quote
from pymongo import MongoClient
"""
简书搜索爬虫
输入搜索关键词,将搜索到的所有文章爬取下来
数据保存到 mongodb 中
"""
class JianshuSearch(object):
def __init__(self, db_name, coll_name, key, host='127.0.0.1', port=27017):
self.headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/57.0.2987.110 Safari/537.36"
}
self.url = 'http://www.jianshu.com/search/do?q={key}&type=note&page={page}&order_by=default'
self.key = quote(key)
self.start_page = 1
self.host = host
self.port = port
self.db_name = db_name
self.coll_name = coll_name
def get_total_pages(self):
'''提取总页码数'''
url = self.url.format(key=self.key, page=self.start_page)
html = requests.get(url, headers=self.headers).text
data = json.loads(html)
total_pages = data['total_pages']
return total_pages
def get_infos(self, page):
'''提取单个页面的文章信息,格式为dict'''
url = self.url.format(key=self.key, page=page)
html = requests.get(url, headers=self.headers).text
data = json.loads(html)
entries = data['entries']
for each in entries:
self.save_infos(each)
def save_infos(self, entry):
'''保存一个文章的信息'''
coon = MongoClient(host=self.host, port=self.port)
coll = coon[self.db_name][self.coll_name]
coll.insert(entry)
def main(self):
'''主函数,循环迭代进行翻页,提取所有页码的信息并保存到数据库'''
total_pages = int(self.get_total_pages())
for i in range(1, total_pages + 1):
self.get_infos(i)
print('总计{}页,已经爬完{}页'.format(total_pages, i))
if __name__ == '__main__':
DB_NAME = 'jianshu'
COLL_NAME = 'search_result'
key = 'python'
spider = JianshuSearch(key=key, db_name=DB_NAME, coll_name=COLL_NAME)
spider.main()
爬虫思路
基本思路
爬虫的基本思路依然分为3个主要步骤:
- 找到入口,即起始页面的 URL
- 找到翻页的规律
- 选择有效的方式进行翻页,提取所有信息
查看页面主要信息
首先在简书的搜索框中输入任意一个关键词,例如“Python”,然后点击搜索按钮,查看结果页面。
结果页面如下图所示:
可以看到,搜索的结果包含了很多种类的信息,有“相关用户”、“相关专题”和“相关文章”,并且文章的排序还可以选择条件。
由于这里只需要提取搜索文章的信息,因此可以先看一下搜索到的文章列表中包含哪些可以收集的信息,以便后续查找和保存数据。
找到入口——第一个 URL
当然,一般来说,很多时候,我们看到的信息其实并不是眼前的页面给我们的信息,而是当前页面从其他地方“搬”过来的信息。如果使用或者知道ajax 的用法,这句话就很好理解,当然,不理解也无妨,并不影响后续操作。
打开浏览器的开发者界面,可以使用快捷键 F12。刷新一下搜索的页面,然后进入 Network,看看浏览器中加载了什么页面。
一般可以先从 Doc中查看,也就是网页源代码,但是这次的爬虫可以看到源代码中并没有搜索的结果页,于是可以继续查看 js,发现好像也没有新加载页面,继续查看 XHR,终于找到了刷新页面加载出来的页面请求,具体看截图:
看截图中的编号,编号1就是页面加载的地方,编号2可以看到请求的 URL,编号3就是 URL 的组成,可以从中查看链接的构成规律,这里的规律就是链接中有2个关键参数,第一个参数就是搜索的关键词,这里用 q=Python表示,然后第二个参数就是当前页码,这里是 page=2,从这里就可以直接联想到,如果知道总页码数,那就可以使用循环来得到所有页码的 URL。编号4是请求的方式,这里是 GET。
选择翻页方式
经过这个页面,就可以把整个爬虫的思路理清楚了:
- 首先,想办法找到总页码数
- 然后提取单页的信息,并且保存到数据库中
- 使用循环方式,提取所有页的信息
源码解读
首先需要导入相关库:
import requests
import json
from urllib.parse import quote
from pymongo import MongoClient
这4个库的作用分别是:
- 网页请求
- 将字符串格式转换成 json 格式
- 将一般字符串转换成 URL 可以读取的格式
- python 连接 mongodb 的依赖
爬虫类的创建
由于这个爬虫是创建了一个爬虫类,所以需要按照 Python类的规范来,首先初始化类,通过函数来实现:
def __init__(self, db_name, coll_name, key, host='127.0.0.1', port=27017):
self.headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/57.0.2987.110 Safari/537.36"
}
self.url = 'http://www.jianshu.com/search/do?q={key}&type=note&page={page}&order_by=default'
self.key = quote(key)
self.start_page = 1
self.host = host
self.port = port
self.db_name = db_name
self.coll_name = coll_name
这里给类传递了5个参数,其中3个是没有给予默认值的参数,2个是给了默认值的参数,每个参数的含义:
- key:搜索的关键词
- db_name:数据库名称
- coll_name:数据表名称
- host,port:链接数据库的地址和端口
提取总页码数
可以通过爬取任意一个页码,然后打印一下提取的信息,看看信息的构成。
通过打印信息可以发现,每个页面的信息都是一个字典格式,因此可以通过json模块来转换成 Python 的 dict 格式,然后可以发现每页都有一个参数是“total_pages”,这个参数就提供了当前搜索的结果总页码数,因此可以通过函数来提取这个参数的值:
def get_total_pages(self):
'''提取总页码数'''
url = self.url.format(key=self.key, page=self.start_page)
html = requests.get(url, headers=self.headers).text
data = json.loads(html)
total_pages = data['total_pages']
return total_pages
提取当个页面的信息,并保存到数据库
由于每个页面的信息都是一个 json 格式,所以信息的提取方式很简单,直接从字典中提取就行了:
def get_infos(self, page):
'''提取单个页面的文章信息,格式为dict'''
url = self.url.format(key=self.key, page=page)
html = requests.get(url, headers=self.headers).text
data = json.loads(html)
entries = data['entries']
for each in entries:
self.save_infos(each)
函数的思路很简单,首先通过 requests
请求网页,然后得到一个json类型的信息,但是由于这个信息的格式是 str
, 所以需要使用 json.loads
方法转换成 dict
格式。然后使用字典的键值对关系提取到总页码数就可以了。
这个函数的最后面用到了一个保存信息的函数,也就是后面要说的将数据保存到数据库中。
def save_infos(self, entry):
'''保存一个文章的信息'''
coon = MongoClient(host=self.host, port=self.port)
coll = coon[self.db_name][self.coll_name]
coll.insert(entry)
保存到数据库的函数需要传入一个参数,是一个 dict
类型,这个参数正好可以理解为提取的单个文章的信息。
首先,按照 mongodb
的连接方式建立一个连接,这个连接就使用到了创建类的时候传入的数据库的参数,然后使用 insert()
方法就可以插入数据了。
循环提取所有页面的信息
讲过上述的的函数,已经可以提取总页码数,也可以提取并保存单个页码的文章信息了,剩下的就是使用循环来提取所有页码的信息了,于是,可以把这个过程写到一个主函数中:
def main(self):
'''主函数,循环迭代进行翻页,提取所有页码的信息并保存到数据库'''
total_pages = int(self.get_total_pages())
for i in range(1, total_pages + 1):
self.get_infos(i)
print('总计{}页,已经爬完{}页'.format(total_pages, i))
为了方便查看爬虫的进度,在每爬取一页信息,可以打印当前的进度。
运行爬虫
最后一段代码就是运行爬虫,首先给定一些需要传递到爬虫类中的参数,然后启动爬虫的主程序即可看到爬虫结果。
查看数据库中信息
“Python”这个字段在简书中有100页的数据(如果没有猜错,这个100页应该是简书默认提供的最大页码数),所以爬虫运行的时间并不长,爬完之后可以使用可视化工具看看数据库中的信息。
数据库中信息如图:
后记:从数据库中可以查看到一些有效信息,包括文字的标题、链接、发布时间、作者的信息、评论数、阅览量、喜欢数等。虽然这些数据并没有什么研究价值,但是本着练手的想法,后续会使用 Python 基本的数据分析工具来对爬取的信息进行可视化分析。