在网页抓取中,常会遇到解析结果列表非常长且包含混合信息的情况。
例如,您可能已经注意到,我们之前提取的图片可能包含 alt 属性,也可能不包含。
或者设想我们要从文章中提取所有链接。众所周知,维基百科文章包含大量链接,而我们可能并不需要完整的列表。结果中将包含外部链接、内部链接、参考文献和引用,因此我们需要将其分类为多个类别。
为解决这个问题,我们将使用 lambda 函数。基本上,lambda 会将结果列表中的每个元素作为参数,并应用我们定义的条件,就像使用 filter 函数一样。
以一个实际案例为例,假设我们需要提取所有内部链接,访问对应文章,并为每篇撰写摘要。考虑到 Python 的应用场景之一是人工智能,这个示例可以成为获取训练数据的绝佳应用。
首先,我们需要安装 NLTK 库,因为生成摘要涉及自然语言处理。
pip install -U nltk
当然,还需要在代码中导入该库:
import re
import nltk
import heapq
# need to download only for the first execution
# warning: the size of the dataset is big; hence it will take time
nltk.download()
注意:如果您是 macOS 用户,可能会遇到“SSL: certificate verify failed”错误。原因可能是 Python 3.6 使用了内置版本的 OpenSSL。您只需打开 Python 的安装目录,并运行以下文件:
/Your/Path/Here/Python 3.6/Install Certificates.command
如您所见,我们还导入了 re 库(用于正则表达式操作)以及 heapq(一种堆队列的实现)。
很好,我们已经具备了编写代码所需的一切。让我们先从提取内部链接开始。如果你回到浏览器,会注意到我们感兴趣的这些元素有几个特点。
这些特征包括:
- href 属性具有值;
- href 属性的值以“/wiki/”开头;
- 链接的父元素是 标签;
这些特征将帮助我们从所有其他链接中区分出我们需要的链接。
既然我们已经知道如何查找链接,接下来看看如何提取它们。
count = 0
def can_do_summary(tag):
global count
if count > 10: return False
# Reject if parent is not a paragraph
if not tag.parent.name == 'p': return False
href = tag.get('href')
# Reject if href is not set
if href is None: return False
# Reject is href value does not start with /wiki/
if not href.startswith('/wiki/'): return False
compute_summary(href)
return True
def extract_links(soup):
soup.find_all(lambda tag: tag.name == 'a' and can_do_summary(tag))
def main():
URL = 'https://en.wikipedia.org/wiki/Beer'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')
extract_links(soup)
main()
好的,这里发生了什么?观察 extract_links() 函数,我们可以发现,我们没有传入标签名称,而是将一个 lambda 函数作为参数传递给了 .find_all() 方法。这意味着,我们从整个 HTML 文档的所有标签中,只挑选出符合我们条件的那些。
如你所见,标签的筛选条件是:必须是链接,且必须通过上面定义的 can_do_summary() 函数的验证。在那里,我们会剔除所有不符合先前观察到的特征的链接。此外,我们使用了一个全局变量来将提取的链接数量限制为 10 个。如果你需要所有链接,请随意移除 count 变量。
最后,我们对新发现的链接调用 compute_summary() 函数。该函数负责对文章进行摘要处理。
def compute_summary(href):
global count
full_link = 'https://en.wikipedia.org' + href
page = requests.get(full_link)
soup = BeautifulSoup(page.content, 'html.parser')
# Concatenate article paragraphs
paragraphs = soup.find_all('p')
article_text = ""
for p in paragraphs:
article_text += p.text
# Removing Square Bracket, extra spaces, special characters and digits
article_text = re.sub(r'\[[0-9]*\]', ' ', article_text)
article_text = re.sub(r'\s+', ' ', article_text)
formatted_article_text = re.sub('[^a-zA-Z]', ' ', article_text)
formatted_article_text = re.sub(r'\s+', ' ', formatted_article_text)
# Converting text to sentences
sentence_list = nltk.sent_tokenize(article_text)
# Find frequency of occurrence of each word
stopwords = nltk.corpus.stopwords.words('english')
word_frequencies = {}
for word in nltk.word_tokenize(formatted_article_text):
if word not in stopwords:
if word not in word_frequencies.keys():
word_frequencies[word] = 1
else:
word_frequencies[word] += 1
maximum_frequency = max(word_frequencies.values())
for word in word_frequencies.keys():
word_frequencies[word] = (word_frequencies[word] / maximum_frequency)
# Calculate the score of each sentence
sentence_scores = {}
for sent in sentence_list:
for word in nltk.word_tokenize(sent.lower()):
if word in word_frequencies.keys():
if len(sent.split(' ')) < 30:
if sent not in sentence_scores.keys():
sentence_scores[sent] = word_frequencies[word]
else:
sentence_scores[sent] += word_frequencies[word]
# Pick top 7 sentences with highest score
summary_sentences = heapq.nlargest(7, sentence_scores, key=sentence_scores.get)
summary = '\n'.join(summary_sentences)
count += 1
简而言之,我们向新发现的 URL 发起 HTTP 请求,并将结果转换为 BeautifulSoup 对象,就像我们在文章开头所做的那样。
为了生成摘要,我们会从文章中提取所有段落并将其拼接在一起。随后,我们会移除所有可能干扰计算的特殊字符。
简而言之,摘要的生成是通过计算高频词,并根据各句子中词汇的出现频率为其赋予分数来实现的。最后,我们选取分数最高的7个句子。
虽然这不是本文的重点,但如果您对自然语言处理感兴趣甚至充满热情,可以在此处阅读更多相关内容。