新手向爬虫(一)利用工具轻松爬取简书并分析

爬取目标

爬取四块简单的简书网页,并做一定的分析。

  • 第一块是首页热门,网址就是
    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 requestspip 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.whlx为 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中运行,截图如下:


推荐阅读更多精彩内容