爬取目标
爬取四块简单的简书网页,并做一定的分析。
- 第一块是首页热门,网址就是
http://jianshu.com
- 第二块是简书推荐,网址形如
http://www.jianshu.com/recommendations/notes?max_id=1477985000
- 第三块是热门专题页,网址形如
http://www.jianshu.com/collections?order_by=score&page=1
- 第四块就是简书专题文章,网址形如
http://www.jianshu.com/collections/16/notes?order_by=added_at&page=1
网络请求和解析使用库
- 从最简单的开始,使用最好用的
requests
做网络访问,并且使用操作方便的beautifulsoup
来解析html。 - 没有的话直接
pip install requests
和pip install bs4
安装。
数据存储
- 直接存储为csv。在Excel文件中打开并分析。
- 此外尝试使用下tinydb,一个文档型数据库。操作简易。(只在部分内容使用到)
首先我们做最简单的第四块
- 从网址
http://www.jianshu.com/collections/16/notes?order_by=added_at&page=1
的结构,就可以看出我们只需要修改网址末尾的page=x
就可以不断地访问新的页面爬取这个专题下的文章了,collections/
后面的16
代表程序员专题,order_by=added_at
代表按收录时间排序。下面就开始爬取吧。
import requests
from bs4 import BeautifulSoup
# 引入库
base_url = 'http://www.jianshu.com/collections/16/notes?order_by=added_at&page=%d' # page=0 和 page=1的效果相同
add_url = 1 # page数
num = 0 # 文章标题计数
while(True):
try:
page = requests.request('get', base_url% add_url).content
# 将基础网址和页数拼接得到实际网址,使用request的get方法获取响应,再用content方法得到内容
soup = BeautifulSoup(page)
# 使用BeautifulSoup分析页面,这里并没有指定解析器,会自动选取可获得的最优解析器
# BeautifulSoup会报一个warning要你选择解析器,可以忽略
article_list = [i.get_text() for i in soup.select(".title a")]
# 这里使用了BeautifulSoup的选择器soup.select,它会返回一个被选取的对象列表,我们使用列表推导,对每个对象获取其中的文字再组成列表。
# 选择器使用见下文
for i in article_list:
# 将文章列表打印出来
num+=1
print(num, ' ', i)
add_url += 1
except Exception as e:
# 异常分析,打印出异常信息
print(e)
break # 这里使用break退出循环,也可注释掉该句,不断循环
-
结果如下:
-
可以看出我们成功地爬取了内容。其实本次爬取的关键在于soup.select选择器的内容。而选择器内容该如何获取呢,它类同css选择器(规则如下),我们可以分析网页源码,根据规则写出。
-
但是我们还可以使用更便捷的方法,不需要知道额外的知识就解决,那就是使用SelectorGadget,它的chrome插件,非常好用,安装后点击使用,然后直接在你要爬取的网页上选择你要爬取的内容,这时一系列内容会被标黄,但是可能其中有些你不需要的内容,再点一下它们,它们会被标记为红色,就不会被选择器选中,这时你所需要的选择器内容就在下方显示出来了,如下图,黄色的标题就被选中,最下的蓝色选中文字即为自动生成的选择器内容:
-
这样,我们成功解决了选择器的问题,获取到了我们需要的内容。
-
其实还有一个关键点在于这样的网址是如何获取的呢,一般我们访问简书可能并不会遇到这样形式的网址,下文中我们会说明。
接着让我们来看看首页热门文章
- 我们先通过上面的方法获取文章标题的选择器,它也是
soup.select(".title a")
。 - 和上面不同的是,首页的网址只有一个,也没有一个明显的页数递增规律,但是首页下方会有一个更多的按钮。让我们使用F12开发者工具选取审查元素(快捷键Ctrl+Shift+C)选中按钮查看这个按钮的信息。
- 我们可以看到button标签里有个
data-url
属性,它是一个相对路径,末尾是page=2
,将它加上简书域名,尝试一下,发现就是下一页的信息。于是,我们可以通多获取button标签,读取它的属性来访问以继续爬取。 - 此外,我们升级一下解析器,让html的解析速度变快些,下载lxml(我下载的是cp35-cp35m-win_amd64.whl版本,安装成功),使用
pip install x.whl
(x
为 whl文件名)安装。然后我们可以在Beautifulsoup中指定解析器了。 - 这次我们将爬取到的首页热门文章标题信息保存为csv文件,以供后续处理。
import requests
from bs4 import BeautifulSoup
import csv # 引入csv模块,读写csv文件
import datetime # 引入日期时间模块
base_url = 'http://jianshu.com'
def shouYeReMen():
add_url = '' # 先置为空字符串
title_list = [] # 文章标题列表
for i in range(15): # 首页热门实际为15页,一开始可以设大些让程序自动退出来看到底有多少页
try:
first_page = requests.get(base_url+ add_url).content
soup = BeautifulSoup(first_page, "lxml")
title_list += [i.get_text() for i in soup.select(".title a")]
# 以上和上一个爬虫类同
try:
# print(soup.select(".ladda-button"))
add_url = soup.select(".ladda-button")[-1].get("data-url")
# 使用get方法获取button的标签属性信息作为额外的url
# 这里使用try,在最后一页没有button时会抛出异常
except:
# 在异常处理中通过break退出循环
break
except requests.exceptions.ConnectionError: # 有可能请求过多导致服务器拒绝连接
status_code = "Connection refused"
print(status_code)
time.sleep(60)
# 我们睡眠一分钟后再爬
with open(datetime.datetime.now().strftime('%Y_%m_%d-%H_%M_%S')+'.csv', 'w', newline='', encoding='utf-8') as f:
# 在脚本当前文件目录下新建以当前时间命名的csv文件,使用utf8编码
writer = csv.writer(f)
# 传入文件描述符构建csv写入器
writer.writerow(title_list)
# 将本次爬取的所有文章列表写在一行中
print("have_done!")
import time
# 最后,我们循环爬取首页信息,为了减轻服务器负担,也避免爬虫请求被拒绝,我们每隔30秒爬取一次
while True:
shouYeReMen()
time.sleep(30)
-
爬取结果如下
- 现在我们来处理下这些数据,使用分词并保存热点词汇,在excel中查看热点词随时间变化情况。
- 我们使用友好的中文分词库结巴分词。使用
pip install jieba
安装。
import jieba
from collections import Counter
# 引入便捷的Counter函数,可以看作一个计数器,计算元素在列表中的出现次数
# Counter([1,2,3,1])的输出为Counter({1: 2, 2: 1, 3: 1})
import csv
def word_frequency(text):
# 定义词频统计函数
words = [word for word in jieba.cut(text, cut_all=False) if len(word) >= 2]
# 这里我们直接使用jieba.cut函数来分析文本的词汇,为了方便,我们先选取2个字以上的词统计
# 进一步可以自定义词库,去掉与增加某些词
c = Counter(words)
# 统计词频,并利用Counter的方法返回频率最高的18个(词汇,频率)的元组列表
return c.most_common(18)
import os; os.chdir(r'E:\python\shouye')
# 这里我使用了系统库,改变当前所处路径,也就是改到下面读取和保存文件的路径
import glob
result = open('result.csv', 'a', newline='', encoding='gb2312')
# 这里编码为gb2312,以便直接在excel中打开查看
writer = csv.writer(result)
filelist = glob.glob('2016_*.csv')
# 使用glob获取当前路径下所有以2016_开头的csv文件名的列表,顺序为文件名顺序
num=1 # 打印输出计数
for file in filelist:
with open(file, 'r', encoding='utf-8') as f:
line = f.readline()
# 读取存有信息的一行
row = [(word+'-'+str(freq)) for word, freq in word_frequency(line)]
writer.writerow(row)
# 以 word, freq = (w, f) 解包返回的词频数据,并组合word和freq(使用str()将freq由数字转化为字符串类型),写入文件
print("haha",num)
num+=1
result.close()
# 记得关闭文件
- 最后我们打开EXCEL查看result.csv文件,得到如下结果。每条记录的间隔是半分多钟,可以看到简书的出现频率还是挺高的(在我爬的600条数据中,'简书'几乎一直是热词),估计是目前的简书程序员专题活动的影响(它们的文章标题都包含'简书'),同时看出'人生','如何'之类的词出现频率也不低。
间隔约半分钟的简书热词变化
再进一步,爬取第三块-热门专题页
-
我们在浏览器中来到简书的专题广场页面,再次打开发者工具,点击红框切换到Network分析界面。
-
然后向下滚动简书页面,一小段之后,我们发现网络请求栏有新的条目出现,而变化的第一个的名称就是我们需要的请求地址了。
- 我们直接copy链接,获得
http://www.jianshu.com/collections?page=2&_=1478182353282
Paste_Image.png - 我们只取
&
前面部分就可以访问了,在浏览器里尝试下,ok。这就是获取网络请求地址的最常用方法了。 - 类似的,在收集到请求信息后我们尝试获取所有简书热门专题信息:
from tinydb import TinyDB
db = TinyDB('collection_db.json')
# 这里我们尝试下tinydb,一句话就获得了新建的数据库对象
import csv
csvfile = open("1.csv", "w", encoding='utf-8') # 先保存为utf8编码,以免出现gbk编码错误
fieldnames = ["cname", "href", "focus_number", "essays", "hot_url", "new_url"] # 设定Excel文件的列表头
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
# 这里我们获取csv的字典插入器,可以直接插入字典,键为列表头中的内容
writer.writeheader()
# 写入列表头
import requests
from bs4 import BeautifulSoup
import re
reg = re.compile('(\d+)篇')
# 使用正则匹配来获取篇数,(\d+)表示多个数字
base_url = "http://jianshu.com"
collectionlist_url = "http://www.jianshu.com/collections?order_by=score&page=%d"
# collection_url = 'http://www.jianshu.com/collections/%d/notes?order_by=added_at&page=1'
add_url = 1
# collection_num = 1
collection_selector = "h5 a" # 专题名
focus_on_selector = ".follow > span" # 关注人数
total_essay_selector = ".blue-link" # 总篇数
hot_selector = ".top-articles a" # 热门排序
new_selector = ".latest-articles a" # 最新收入
total_collection_list = []
while(add_url < 300):
try: # 请求并获取各种数据
first_page = requests.request('get', collectionlist_url% add_url).content
soup = BeautifulSoup(first_page, "lxml")
collection_list = [[i.get_text(),i.get('href')] for i in soup.select(collection_selector)] # 一组24个
if not collection_list : break
focus_on_list = [i.get_text() for i in soup.select(focus_on_selector)]
focus_on_list = [int(float(i.replace('K', ''))*1000) if ('K' in i) else int(i) for i in focus_on_list]
# 对尾缀K处理,将K去掉,将数字乘以1000
total_essay_list =[ int( reg.findall(i.get_text())[0] ) for i in soup.select(total_essay_selector)]
# 使用zip将多个可迭代对象合并在一起使用
for c,f,e in zip(collection_list, focus_on_list, total_essay_list):
c.append(f);c.append(e)
second_page = requests.request('get', base_url+c[1]).content
# 访问专题页面,获取该专题的热门链接和最新加入文章页的链接。
soup = BeautifulSoup(second_page, "lxml")
hot_url = soup.select(hot_selector)[0].get('href')
new_url = soup.select(new_selector)[0].get('href')
c += [hot_url, new_url]
for i in collection_list:
#print(add_url, ' ', i)
collection_dict = {"cname": i[0], "href":i[1], "focus_number":i[2], "essays":i[3], "hot_url":i[4], "new_url":i[5]}
db.insert(collection_dict)
# 一句话插入数据库
writer.writerow(collection_dict)
# 写入csv文件
print(add_url)
add_url += 1
total_collection_list += collection_list
except Exception as e:
print(e)
break
csvfile.close()
# 关闭文件
- 这里由于编码问题,直接使用excel打开csv文件会出现乱码。我们采取另外一个简单方式,直接使用该网站
将之前存入的数据库文件collection_db.json拖入并下载得到我们需要的良好编码的csv文件。打开结果如下:
- 以专题关注人数排序:
-
以关注人数/文章数比率排序:
- 结果还是挺有趣的。
- 注意,我们同时还获取了各个专题的热门和最新加入链接。这就是我们一开始爬取的专题链接的来源了。
最后轻松一下,完成推荐文章爬取
- 我们来到简书推荐页面
http://jianshu.com/recommendations/notes
,也就是新上榜
,发现最下有个button,我们可以使用类似于首页热门的爬取方式。代码基本相似,就不做注释了。
import requests
from bs4 import BeautifulSoup
base_url = 'http://jianshu.com'
add_url = '/recommendations/notes'
num = 0
while(True):
try:
first_page = requests.request('get', base_url+ add_url).content
soup = BeautifulSoup(first_page, "lxml")
title_list = [i.get_text() for i in soup.select(".title a")]
for i in title_list:
num+=1
print(num, ' ', i)
# print(title_list)
try:
# print(soup.select(".ladda-button"))
add_url = soup.select(".ladda-button")[-1].get("data-url")
except:
break
except Exception as e:
print(e)
break
-
注意,如果直接在windows控制台输出,会出现编码错误,我在Ipython中运行,截图如下:
网友评论
给大神点赞,👍!
给大家推荐一款企业级别的爬虫差产品:瑞雪采集云
推荐使用瑞雪采集云,学习简单了解java或者python就可以使用平台
瑞雪采集云
瑞雪采集云第一个支持java和python的企业级的开发平台。
瑞雪采集云的特点:
1. 支持java和python
2. 采集效率高
3. 分布式
4. 任务管理机制强大
5. 部署简单易用
6. 自动学习机制
7,费用低
web2data.com
with open() as f: # 有重新编码的 encoding='utf-8'
请问博主怎么解决