0%

Python爬虫初探——爬取百度百科词条

什么是爬虫?按照我目前的理解:爬虫是一段脚本程序,能够自动从网站上下载网页,解析网页内容,并分析提取出所需要的数据。它最大的特点是能够自动循环运行,一个个遍历相关网页,可以抓取相当大的数据量。

就如同互联“网”上的”蜘蛛,巡视着这网上的每一个节点,择机捕获猎物。

第一次接触爬虫,我是看的慕课网上的Python开发简单爬虫。这个教程讲解了爬虫的基本知识,并介绍了一种逻辑非常清晰的爬虫架构,实现了从百度百科上爬取1000个词条和及其简介数据,输出到一个HTML文件里。非常的实用,但是对于Python新手的我来讲还是有点复杂。代码量还不够,对于Python中的类理解还不够深。所以我就自己鼓捣,从最简单的开始写。

本篇就记录我从零开始,自己摸索,直至能够如写出上述教程中结构清晰的爬虫的一整个过程。

一、拟定需求

开始写一段程序之前,要明白自己的需求是什么。单纯一个“爬取百度百科词条”一句话,可以是客户给的需求,但是作为实际编程的人而言,要把需求细化到每一个步骤,然后再根据这些步骤一一去实现,最终达到最后的目标。下面是我初步拟出的需求:

1
2
3
4
5
6
7
# 制作一个百度百科爬虫
# 基本功能:
# 1.手动给定初始页面URL
# 2.自动下载初始页面
# 3.分析初始页面中包含的其他链接
# 4.依次自动下载并分析各链接的网页
# 5.依次打印出各链接对应的词条名

二、爬虫 Version 1.0 单次下载

由于很多模块的使用还不熟悉,直接实现上述需求对我来说还是有点困难,故而先实现一个无循环的简单功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#V1.0 给定一个百度百科的页面,打印出该页面的词条
import urllib2
import bs4

def download(url): #将主要功能包装成一个函数
Page = urllib2.urlopen(url) #下载对应的页面,作为Page对象
#将页面的内容作为soup对象,并指定html.parser解析器
soup = bs4.BeautifulSoup(Page.read(),'html.parser')
print soup.h1.string #打印出页面的词条


root_url = r'https://baike.baidu.com/item/Python' #提供要下载的网页链接

download(root_url) #运行

这段简单的代码中导入了两个模块,urllib2是内置模块,用来实现下载指定链接的网页,bs4是第三方模块,用来对网页内容进行解析。这样就实现了爬虫最最基本的功能:下载指定网页——解析网页内容——提取出所需要的信息。

三、爬虫 Version 2.0 实现循环

上面只是一个简单的单次下载的过程。下面要实现爬虫的自动循环的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#V2.0 完成爬虫的初始形态
import sys
reload(sys)
sys.setdefaultencoding( "utf-8" ) #解决编码格式问题,还不知道为什么,网上查的

import urllib2
import bs4
import re

urltogo = set() #定义 将要访问的链接 集合
urlhasgone = set() #定义 已经访问过的链接 集合
n = 0 #定义计数器

def download(url): #对应V1.0中的下载功能
Page = urllib2.urlopen(url)
soup = bs4.BeautifulSoup(Page.read(),'html.parser') #下载网页

#下面是提取网页中的百科词条链接,作为我们接下来要访问的网页
for item in soup.find_all('a',href=re.compile(r"/item/")): #提取页面链接
url = "https://baike.baidu.com"+item['href'] #拼接完善链接
#判断是否在已访问的集合中,如果没有,加入到将要访问的集合中
if not url in urlhasgone:
urltogo.add(url)

#下面是提取网页的词条信息
global n #声明全局变量
print soup.h1.string,n,'has crawled',len(urltogo),'left to crawl'

urlhasgone.add(url) #将此下载完的链接放入已访问的集合

def crawler(url): #爬虫主程序,主要完成循环调度功能
urltogo.add(url) #将初始链接放入将要访问的集合中
while True:
next = urltogo.pop() #提取一个将要的访问的链接
global n
n = n+1 #计数器计数
download(next) #下载并解析该网页

#添加了一个输入命令,可以让用户自定义初始链接
keyword = raw_input("Please input a English word you want to research: ")

root_url = r'https://baike.baidu.com/item/'+keyword

crawler(root_url)

#注意!!!!!迭代set的过程中不能更新set

导入了内置的sys模块解决编码问题(但是还不知道原理,已经遇到过好几次,下次单独研究一下),导入内置的re实现模糊匹配功能。如上所示,已经基本实现了一个爬虫的雏形。但是实际运行过程中,大约爬取两千个词条左右就会报错,应该是百度的反爬虫功能导致的。下面应该添加异常处理。然后再添加输出功能。

四、爬虫 Version 3.0 初具雏形

基本实现了爬虫功能后,我尝试将上述代码进行重构,按照不同的功能分为Url管理器,网页下载器,网页解析器,输出器,总调度程度五个部分分开编写,即Python开发简单爬虫中的架构。这样做的好处是将不同的功能模块分开管理,方便之后维护和代码的重用。最终,运行下面的SpiderMain.py程序,能够成功的下载1000个相关百度百科页面,并提取出其中的词条条目、词条简介、相关词条链接,最终输出到datas.xlsx文件中。
UrlManager.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- coding: utf-8 -*-

# URL管理器
# 要实现的功能:
# 1.添加一个url
# 2.添加一批url
# 3.给出一个新的url
# 4.判断是否存在新的url

class URL(object):
def __init__(self):
self.oldurls = set()
self.newurls = set()

def add_new_url(self,url):
if url not in self.oldurls:
self.newurls.add(url)

def add_new_urls(self,urls):
for url in urls:
if url not in self.oldurls:
self.newurls.add(url)

def get_new_url(self):
url = self.newurls.pop()
self.oldurls.add(url)
return url

def has_new_url(self):
return len(self.newurls) != 0

HtmlDownloader.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding: utf-8 -*-

# 网页下载器
# 要实现的功能:
# 给定一个url:
# 1.下载页面,判断是否下载成功,返回页面内容

import urllib2

class DOWNLOAD(object):
def download(self,url):
response = urllib2.urlopen(url)

if response.getcode() != 200:
return None

return response.read()

HtmlParser.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# -*- coding: utf-8 -*-

# 网页解析器
# 要实现的功能:
# 给定网页内容:
# 1.解析出网页的所有百科词条链接,并拼接成完整链接,封装成一个list
# 2.解析出网页的词条名及词条简介

import re
import bs4

class PARSER(object):
def get_new_urls(self,soup):
newurls = set()
for item in soup.find_all('a',href=re.compile(r"/item/")):
url = "https://baike.baidu.com"+item['href']
newurls.add(url)
return newurls

def get_new_data(self,url,soup):
newdata = {}
newdata['url'] = url
newdata['item'] = soup.h1.string
newdata['urls'] = len(self.get_new_urls(soup))
summary_node = soup.find('div', class_='lemma-summary')
newdata['summary'] = summary_node.get_text()
return newdata

def parser(self,url,page):
soup = bs4.BeautifulSoup(page,'html.parser',from_encoding='utf-8')
newurls = self.get_new_urls(soup)
newdata = self.get_new_data(url,soup)
return newurls,newdata

HtmlOutputer.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding: utf-8 -*-

# 输出器
# 要实现的功能:
# 1.收集每次下载后的数据
# 2.最后一次性输出到一个xslx文件中

import openpyxl

class OUTPUT(object):
def __init__(self):
self.datas = []

def collect_data(self,data):
self.datas.append(data)

def output(self):
wb = openpyxl.Workbook()
ws = wb.active
ws['A1'] = 'num'
ws['B1'] = 'url'
ws['C1'] = 'item'
ws['D1'] = 'urls'
ws['E1'] = 'summary'
for n,data in enumerate(self.datas):
ws.cell(row = n+2, column = 1).value = n+1
ws.cell(row = n+2, column = 2).value = data['url']
ws.cell(row = n+2, column = 3).value = data['item']
ws.cell(row = n+2, column = 4).value = data['urls']
ws.cell(row = n+2, column = 5).value = data['summary']
wb.save('datas.xlsx')

SpiderMain.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# -*- coding: utf-8 -*-

# 爬虫总调度程序:
# 要实现的功能:
# 1.要求用户输入一个关键词,将关键词拼接成百度百科链接
# 2.将第一个链接传入新链接集合
# 3.循环:判断是否存在新链接——获取一个新链接——用下载器下载该链接——将下载的内容传给解析器——
# 将返回的链接传给URL管理器——将返回的数据传给输出器
# 4.输出文件

# 导入上面四个模块
import UrlManager,HtmlDownloader,HtmlParser,HtmlOutputer

class SPIDER(object):
def __init__(self):
self.URL = UrlManager.URL()
self.DOWNLOAD = HtmlDownloader.DOWNLOAD()
self.PARSER = HtmlParser.PARSER()
self.OUTPUT = HtmlOutputer.OUTPUT()

def crawler(self,root_url):
self.URL.add_new_url(root_url) #把第一个链接放入新链接集合
count = 1 #定义计数器
while self.URL.has_new_url(): #判断是否存在新链接
try:
url = self.URL.get_new_url() #提取一个新链接
print 'Crawl %d %s' %(count,url)
page = self.DOWNLOAD.download(url) #把链接传递给下载器下载,并返回网页内容
#把网页内容传递给解析器,返回新链接集合以及所需数据
newurls,newdata = self.PARSER.parser(url,page)
self.URL.add_new_urls(newurls) #把新链接结合传递给Url管理器添加
self.OUTPUT.collect_data(newdata) #把数据传递给输出器保存
if count == 1000:
break
count = count + 1
except:
print 'Crawl failed!'

self.OUTPUT.output() #写文件

keyword = raw_input("Please input a English word you want to research: ")
root_url = r'https://baike.baidu.com/item/'+keyword
mycrawler = SPIDER()
mycrawler.crawler(root_url)

五、总结

  1. 练习了Python的基本语法,另外学习了几个写爬虫需要用到的库:urllib2——下载网页、bs4,re——解析网页、openpyxl——操作Excel文件。
  2. 学习了爬虫的一种基本架构:把一个爬虫分为Url管理器、网页下载器、网页解析器、输出器、总调度程序五个部分。
  3. 异常处理的思想:最开始是利用if response.getcode() != 200:这个语句简单判断一下网页下载是否成功。然后在总调度程序中,将主要的循环体用一个try语句包含起来,将一次循环的整个过程进行异常判断。其实在课程的源代码中,作者在每个模块的输入部分都加入了异常处理,判断输入为空时,应如何操作。我的代码中暂时没有加入这些。但是显然的,这些异常处理的语句让整个爬虫的容错率得到了提高。