广度优先搜索(BFS)算法,该部分内容涉及到图相关的算法,如果不了解图是什么,请参考书《零基础学数据结构》(C语言版)或清华大学严蔚敏《数据结构》,当然这需要一定的C语言基础,Python相关的数据结构和算法的书我还不知道,如果有,请在评论中告诉我,先谢了。你可能疑问,问什么不学算法而看数据结构相关的书籍呢,因为数据结构和算法是分不开的。
当然,不了解BFS,并不影响我们理解本文的内容,因为不过用了一个队列和一个集合而已。将要访问的网址放入队列中,访问后将新读取的网址进入队列,原网址从队列中弹出放入集合里(队列是前进后出)。只要集合里存在的网址,就不再访问。直到队列控、集合满,遍历结束。这就实现了遍历全网并不重复访问的作用。在这里我们并不讨论该算法的空间效率和时间效率。
下面我们就看下《零基础自学用Python 3开发网络爬虫(二): 用到的数据结构简介以及爬虫Ver1.0 alpha》,哎,不要笑我摘抄代码,学习阶段吗:
import re import urllib.request import urllib from collections import deque queue = deque() visited = set() url = 'http://news.dbanotes.net' # 入口页面, 可以换成别的 queue.append(url) cnt = 0 while queue: url = queue.popleft() # 队首元素出队 visited |= {url} # 存入集合(标记为已访问) print('已经抓取: ' + str(cnt) + ' 正在抓取 <--- ' + url) cnt += 1 urlop = urllib.request.urlopen(url) if 'html' not in urlop.getheader('Content-Type'): continue # 避免程序异常中止, 用try..catch处理异常 try: data = urlop.read().decode('utf-8') except: continue # 正则表达式提取页面中所有队列, 并判断是否已经访问过, 然后加入待爬队列 linkre = re.compile('href=\"(.+?)\"') for x in linkre.findall(data): if 'http' in x and x not in visited: queue.append(x) print('加入队列 ---> ' + x)
urllib.request和urllib.parse,以及re三个模块的作用我们在前一篇文章中已经讲解过了,这里涉及两种没有接触过的数据结构:set和queue,也就是文章前面说的集合和队列,set数据结构保证‘元素’的唯一性,队列数据结构前进后出(跟生活中的‘队列’是一个含义,当然也有双端队列,但这显然不在这篇文章的讨论范围)
-------------------------------华丽分割----------------------------
整个循环内部处理流程:
(1)弹出队首元素(一个网址,一个字符串),将该字符串放入集合中(标记为已经访问)
url = queue.popleft() # 队首元素出队
visited |= {url} # 存入集合(标记为已访问)
(2)抓取弹出的字符串(url)的网页,提取该网页中包含的网址。但是这里有个问题,每个链接对应的不一定的HTML页面(从前一篇文章《Python3写爬虫(二)爬取页面图片》中可以了解,其实提取的链接有可能对应图片,当然对应视频,flash,word等等都有可能),所以需要排除一下,如果页面不是HTML页面,直接舍弃。当然在这里如果我们添加一个if分之,就可以区分该该链接指向的文件类型,分类下载,这不是本文讨论重点,不赘述。
urlop = urllib.request.urlopen(url)#抓取url指向的页面,不赘述
if 'html' not in urlop.getheader('Content-Type'):#判断下页面是否为HTML页面,不是,不理会
continue
data = urlop.read().decode('utf-8')#调整抓取数据的编码格式,这个我一直头疼啊
在windows控制台CMD中输出的页面一直存在乱码的问题,调试了两台,还是如此,后来放弃了,反正以后的数据都是存储在文件和数据库中,输出内容也是在可视化页面或者HTML中,安啦。据说相比Python2,Python3在字符编码中有很大的调整,所有新手入门很多都在字符编码上头疼,当然我入门的是Python3.X。
linkre = re.compile('href=\"(.+?)\"')
linkre.findall(data)
然后,利用上一篇文章中提到的,正则表达式,对字符串进行检索,得到所有的网址数据存入一个字符串列表中(list of string),当然,这个字符串列表中的每一项,都是一个网页链接(href=""),findall()函数相关的内容,详见:https://docs.python.org/3/library/re.html#re.findal
(3)将新得到的字符串列表存入队列中,然后继续下一次循环
queue.append(x)
---------------------------华丽分割----------------------------
当然,这个小程序只是个雏形,便于讲解,Not Elegance。
例如,爬取一段时间页面后,程序退出,控制台显示:
urllib.error.HTTPError: HTTP Error 403: Forbidden
之所以出现上面的异常,是因为如果用 urllib.request.urlopen 方式打开一个URL,服务器端只会收到一个单纯的对于该页面访问的请求,但是服务器并不知道发送这个请求使用的浏览器,操作系统,硬件平台等信息,而缺失这些信息的请求往往都是非正常的访问,例如爬虫。有些网站为了防止这种非正常的访问,会验证请求信息中的UserAgent(它的信息包括硬件平台、系统软件、应用软件和用户个人偏好),如果UserAgent存在异常或者是不存在,那么这次请求将会被拒绝(如上错误信息所示)(以上文字摘自《Python 3.x 中"HTTP Error 403: Forbidden"问题的解决方案》)
要想解决这一问题,就需要利用伪浏览器的方式浏览网页页码,也就是在请求中加入UserAgengt的信息,后面内容中,详细讲解。