跳至主要内容

用 Python 进行网页抓取与自然语言处理(NLP)

学习从网页抓取小说并绘制词频分布;您将实践使用 Python 软件包 requests、BeautifulSoup 和 nltk。
· 14分钟 阅读

使用 AI 探索

nltk Python

本周早些时候,我做了一场 Facebook Live 编码跟练。在其中,我们用一些基础的自然语言处理方法,绘制了小说《白鲸》中最常出现的单词。在此过程中,我们也体会到时刻关注流程、以如下数据科学流水线来思考问题的有效性:

  1. 提出您的问题;
  2. 获取您的数据;
  3. 整理数据以回答您的问题;
  4. 回答您的问题;
  5. 呈现您的解决方案,让他人能够理解。

在本文中,您将学习如何构建一个数据科学流水线,以绘制《白鲸》(以及许多其他小说)的词频分布。

提示:如果您想回看这场 Facebook Live,请查看下面的视频;您可以直接跳到第 12 分钟,Hugo 出场并正式开始。

我们不会直接给您小说文本:您将学习如何使用 Python 包 requests 从网站 古腾堡计划(基本上是一个大型书籍语料库)抓取小说,并使用 BeautifulSoup 从这些网页数据中提取小说文本。然后,您将深入使用自然语言工具包(nltk)来分析这些小说。在此过程中,您会了解自然语言处理(NLP)中的重要概念,如分词与停用词。

完成之后,您将能够可视化古腾堡计划上任意小说的词频分布。而您所掌握的 NLP 技能,同样适用于数据科学家经常遇到的大量数据,因为世界上相当大比例的数据是非结构化数据,其中包含大量文本。

例如,下面这张词频分布图会来自哪部作品呢?

word frequency distribution

本文由一个 Jupyter Notebook 生成;您可以在此仓库中找到它。如果您有任何想法、反馈和/或感想,欢迎在 Twitter 上联系我:@hugobowne。

预备步骤

按照 README.md 中的说明,完成系统环境的准备与配置。

1. 陈述您的问题

在小说《白鲸》中,哪些词出现得最频繁?它们各自出现了多少次?

2. 获取您的数据

您的原始数据是梅尔维尔小说《白鲸》的文本。您会如何把这本约 800 词的书的文本导入到 Python 中?

其实有多种方式,但首先要知道,这份文本在网上的 古腾堡计划上可免费获取。我们去那里,尝试找到《白鲸》,然后将相关的 URL 存入您的 Python 命名空间:

# Store url
url = 'https://www.gutenberg.org/files/2701/2701-h/2701-h.htm'

现在您已经有了 URL,需要去获取该网站的 HTML。

注意:HTML 是超文本标记语言(Hypertext Markup Language)的缩写,是 Web 的标准标记语言。

您将使用 requests 来完成,它是最流行且实用的 Python 包之一。您可以在 DataCamp 的《Python 数据导入进阶(第 2 部分)》课程中了解更多。

beautifulsoup Python

根据 requests 的官网:

Requests 允许您发送天然、有机的 HTTP/1.1 请求,无需手工劳作。

而以下组织声称在内部使用 requests:

英国女王陛下政府、Amazon、Google、Twilio、NPR、Obama for America、Twitter、Sony,以及不愿具名的美国联邦机构。

此外,

Requests 是史上下载量最高的 Python 包之一,每月下载量超过 13,000,000。酷孩子们都在用!

您将对该网站发起一个 GET 请求,这意味着您从它那里“获取”数据。使用浏览器访问网页时,实际上也是在这么做。还有其他类型的请求,例如 POST 请求,但这里不涉及。

requests 通过其 get 函数让这一切变得很简单。现在就发起请求并查看返回对象的类型。

# Import `requests`
import requests

# Make the request and check object type
r = requests.get(url)
type(r)
requests.models.Response

这是一个 Response 对象。您可以在 requests 快速入门指南中看到,Response 对象有一个 text 属性,可以从中获取 HTML!让我们这么做,并打印 HTML 看看:

# Extract HTML from Response object and print
html = r.text
#print(html)

好的!这些 HTML 并不完全就是您要的内容。不过,它确实包含了您要的:《白鲸》的文本。接下来您需要做的是对这份 HTML 进行整理,以从中提取出小说。

3. 整理数据以回答问题

第 1 部分:从 HTML 中获取文本

这里您将使用 BeautifulSoup 包。其官网写道:

beautifulsoup package website

看起来很有希望!

先说一下包名:Beautiful Soup?在 Web 开发中,“tag soup(标签汤)”一词指结构或语法上不规范的网页 HTML 代码。Beautiful Soup 最擅长的,就是把“标签汤”再次变得优雅,并轻松从中提取信息!事实上,使用该包时创建并查询的主要对象就叫 BeautifulSoup。创建“汤”之后,我们可以用它的 .get_text() 方法来提取文本。

# Import BeautifulSoup from bs4
from bs4 import BeautifulSoup


# Create a BeautifulSoup object from the HTML
soup = BeautifulSoup(html, "html5lib")
type(soup)
bs4.BeautifulSoup

从这些 soup 对象中,您可以提取出关于所抓取网站的各种有趣信息,例如标题:

# Get soup title
soup.title
<title>
      Moby Dick; Or the Whale, by Herman Melville
    </title>

或者把标题作为字符串获取:

# Get soup title as string
soup.title.string
'\n      Moby Dick; Or the Whale, by Herman Melville\n    '

或者获取页面中所有 < a > 标签(超链接)中的 URL:

# Get hyperlinks from soup and check out first several
soup.findAll('a')[:8]
[<a href="#link2H_4_0002"> ETYMOLOGY. </a>,
 <a href="#link2H_4_0003"> EXTRACTS (Supplied by a Sub-Sub-Librarian).
         </a>,
 <a href="#link2HCH0001"> CHAPTER 1. Loomings. </a>,
 <a href="#link2HCH0002"> CHAPTER 2. The Carpet-Bag. </a>,
 <a href="#link2HCH0003"> CHAPTER 3. The Spouter-Inn. </a>,
 <a href="#link2HCH0004"> CHAPTER 4. The Counterpane. </a>,
 <a href="#link2HCH0005"> CHAPTER 5. Breakfast. </a>,
 <a href="#link2HCH0006"> CHAPTER 6. The Street. </a>]

您现在想做的是从 soup 中提取文本,而 .get_text() 方法正是为此而生。

获取文本,打印出来,看看它。是您要的内容吗?

# Get the text out of the soup and print it
text = soup.get_text()
#print(text)

注意,现在这几乎就是您所需要的。

它是小说的文本,但开头有一些不需要的内容,末尾也有一些不需要的内容。您可以选择移除它们。不过,这些内容相对于《白鲸》正文来说非常少,因此在第一近似下,保留它们也无妨,本文将采取这种做法。若要得到更稳健的结果,我建议将其移除。

既然您已经获得了目标文本,现在是时候统计每个单词出现的次数,并绘制所需的词频直方图了:自然语言处理来帮忙!

第 2 部分:用 NLP 从文本中提取单词

您将使用自然语言工具包 nltk 来:

  1. 对文本进行分词(这个术语的意思是将文本拆分为词等“标记”);
  2. 移除停用词(如 a、the 等在几乎所有英文文本中大量出现的词)。

步骤 1:分词

您希望对文本进行分词,也就是说,将其拆分为单词列表。实质上,您想根据空白字符将文本分割开来。

为此,您将使用一个强大的工具:正则表达式。正则表达式(简称 regex)是一种字符序列,用于定义搜索模式。它们一向以难懂著称,最好的介绍方式是通过示例。

  • 给定字符串“peter piper picked a peck of pickled peppers”,您想从中提取出所有以字母“p”开头的单词。

匹配所有以“p”开头的单词的正则表达式是“p\w+”。我们来拆解一下:

  • 正则开头的“p”表示只匹配以“p”开头的字符序列;
  • “\w”是一个特殊字符类,匹配任意字母数字 A-z、a-z、0-9 以及下划线;
  • “+”表示前一个正则原子可以重复出现任意多次。这意味着“\w+”会匹配任意长度的字母数字字符与下划线序列。

结合起来,“p\w+”会匹配所有以“p”开头、其后跟随字母数字和下划线的子串。在大多数有意义的英文文本中,这通常对应于以“p”开头的单词。

接下来,作为热身,您将使用 Python 内置包 re,从句子“peter piper picked a peck of pickled peppers”中提取所有以“p”开头的单词。

# Import regex package
import re

# Define sentence
sentence = 'peter piper pick a peck of pickled peppers'

# Define regex
ps = 'p\w+'


# Find all words in sentence that match the regex and print them
re.findall(ps, sentence)
['peter', 'piper', 'pick', 'peck', 'pickled', 'peppers']

看起来不错。现在,如果“p\w+”是匹配以“p”开头单词的正则,那么匹配所有单词的正则是什么?

现在由您来对上面的 Peter Piper 示例句子完成这个任务。

# Find all words and print them
re.findall('\w+', sentence)
['peter', 'piper', 'pick', 'a', 'peck', 'of', 'pickled', 'peppers']

现在您可以对包含《白鲸》文本的字符串 text 做同样的事情:

# Find all words in Moby Dick and print several
tokens = re.findall('\w+', text)
tokens[:8]
['Moby', 'Dick', 'Or', 'the', 'Whale', 'by', 'Herman', 'Melville']

注意:也可以使用 自然语言工具包 nltk 来完成:

# Import RegexpTokenizer from nltk.tokenize
from nltk.tokenize import RegexpTokenizer

# Create tokenizer
tokenizer = RegexpTokenizer('\w+')



# Create tokens
tokens = tokenizer.tokenize(text)
tokens[:8]
['Moby', 'Dick', 'Or', 'the', 'Whale', 'by', 'Herman', 'Melville']

好!您已接近目标。不过请注意,上面示例中的“Or”首字母是大写“O”,而在其他地方可能不是,但“Or”和“or”您希望计作同一个词。因此,您需要构建一个包含《白鲸》所有单词的列表,并将所有大写字母转为小写。字符串方法 .lower() 会很有用:

# Initialize new list
words = []


# Loop through list tokens and make lower case
for word in tokens:
    words.append(word.lower())


# Print several items from list as sanity check
words[:8]
['moby', 'dick', 'or', 'the', 'whale', 'by', 'herman', 'melville']

步骤 2:移除停用词

通常会移除英语中高频出现的词,如“the”、“of”、“a”(称为停用词),因为它们不那么有趣。关于这些技术的更多内容,请查看我们的《Python 自然语言处理基础》课程。

nltk 包提供了英文停用词列表,您现在将其保存为 sw,并打印其前几个元素。

如果这里报错,请运行命令 nltk.download('stopwords') 在您的系统上安装停用词资源。

# Import nltk
import nltk

# Get English stopwords and print some of them
sw = nltk.corpus.stopwords.words('english')
sw[:5]
['i', 'me', 'my', 'myself', 'we']

您希望获得 words 中所有不在sw 中的单词列表。实现方式之一是遍历 words 的所有元素,若该词不在sw 中,就将其加入新列表:

# Initialize new list
words_ns = []

# Add to words_ns all words that are in words but not in sw
for word in words:
    if word not in sw:
        words_ns.append(word)

# Print several list items as sanity check
words_ns[:5]
['moby', 'dick', 'whale', 'herman', 'melville']

4. 回答您的问题

我们的问题是:“在小说《白鲸》中,哪些词最常见?它们出现了多少次?”

现在,您可以用 nltk 仅用两行代码就绘制出《白鲸》的词频直方图。为此:

  • 使用函数 nltk.FreqDist() 创建一个频率分布对象;
  • 使用所得对象的 plot() 方法。
#Import datavis libraries
import matplotlib.pyplot as plt
import seaborn as sns

# Figures inline and set visualization style
%matplotlib inline
sns.set()

# Create freq dist and plot
freqdist1 = nltk.FreqDist(words_ns)
freqdist1.plot(25)

NLP pipeline Python

5. 呈现您的解决方案

有意思的是,在使用 nltk 回答我们的问题时,我们实际上已经用一种便于交流的方式呈现了解决方案:一幅词频分布图!您可以直接读出最常见的词及其频率。比如,“whale”(鲸)是在排除停用词后,小说中最常见的单词(这不意外),出现次数超过 1200 次!

加餐

既然您已经看到古腾堡计划上有很多小说都可以绘制词频直方图,那么把整个流程写成一个函数就很有意义:

 def plot_word_freq(url):
    """Takes a url (from Project Gutenberg) and plots a word frequency
    distribution"""
    # Make the request and check object type
    r = requests.get(url)
    # Extract HTML from Response object and print
    html = r.text
    # Create a BeautifulSoup object from the HTML
    soup = BeautifulSoup(html, "html5lib")
    # Get the text out of the soup and print it
    text = soup.get_text()
    # Create tokenizer
    tokenizer = RegexpTokenizer('\w+')
    # Create tokens
    tokens = tokenizer.tokenize(text)
    # Initialize new list
    words = []
    # Loop through list tokens and make lower case
    for word in tokens:
        words.append(word.lower())
    # Get English stopwords and print some of them
    sw = nltk.corpus.stopwords.words('english')
    # Initialize new list
    words_ns = []
    # Add to words_ns all words that are in words but not in sw
    for word in words:
        if word not in sw:
            words_ns.append(word)
    # Create freq dist and plot
    freqdist1 = nltk.FreqDist(words_ns)
    freqdist1.plot(25)

现在用这个函数来绘制古腾堡计划上其他文本的词频分布:

  • 《傲慢与偏见》:
plot_word_freq('https://www.gutenberg.org/files/42671/42671-h/42671-h.htm')

web scraping Python

  • 《鲁滨逊漂流记》
plot_word_freq('https://www.gutenberg.org/files/521/521-h/521-h.htm')

word distribution tokenize

  • 《钦定版圣经》
plot_word_freq('https://www.gutenberg.org/files/10/10-h/10-h.htm')

word frequency

结语

在本文中,您学习了如何构建数据科学流水线,以绘制《白鲸》(以及许多其他小说)的词频分布。您学习了使用 Python 包 requests 从 古腾堡计划(大型书籍语料库)抓取文本,并使用 BeautifulSoup 从网页数据中提取小说。随后,您使用自然语言工具包(nltk)对小说进行了分析。在此过程中,您了解了自然语言处理(NLP)的重要方面,如分词与停用词。现在,您已经能够可视化古腾堡计划上任意小说的词频分布。而您所发展的 NLP 技能,同样适用于数据科学家经常遇到的大量数据,因为世界上相当大比例的数据是非结构化数据,其中包含大量文本。

本文由一个 Jupyter Notebook 生成;您可以在此仓库中找到它。如果您有任何想法、反馈和/或感想,欢迎在 twitter 上联系我:@hugobowne。也请告诉我您构建的那些很酷的项目。

查看 DataCamp 的《使用 Python(与 Beautiful Soup)进行网页抓取》和《如何使用 Python 抓取亚马逊》教程。

主题