参考资源:
1.Web Scraping with Python&中文版《Python网络数据采集》
2.廖雪峰hashlib教程
项目内容:
维基百科里面有很多内嵌的链接,我们想看看,从某个名人链接出发,到达另外一个链接的界面,需要经过那些链接。
我们项目是从李嘉诚到周杰伦,最后的结果:
['/wiki/Jay_Chou', '/wiki/Liu_Xiang_(hurdler)', '/wiki/Chinese_name', '/wiki/Li_Ka-shing']
总共访问了大概210个网页
项目思路:
这有点像一棵树的遍历,可以层序遍历或者前序遍历,或者说像图的广度遍历和深度遍历,本项目采用广度遍历,因为广度遍历可以计算出最短的计算距离。
代码用一个列表 added_urls
存放已经访问过的链接和待访问的链接,这个过程熟悉图的广度遍历的人应该知道,值得注意的是,为了在找到目的链接时可以追溯到底是经过哪些节点找到目标节点的,列表的每个元素不仅仅保存链接还保存父链接在整个列表中的序号
另外为了方便调试,第一次访问某个网站都会在硬盘保存对应的html文件,文件名为网站链接计算出来的哈希值,后面再访问的时候根据链接首先计算出哈希值,再看看有没有对应的文件,如果有,直接读取硬盘上的文件即可,硬盘加载文件可比网络要快得多。
def get_urls_md5_hash(self,url):
# 根据urls计算哈希值作为文件名
md5 = hashlib.md5()
md5.update(url.encode())
return md5.hexdigest()
# 第一次从网络获取并保存到硬盘,第二次从硬盘读取
def get_urls_html(self,url):
# 检查链接是否加入了'https://en.wikipedia.org/'
url = self.checkurl(url)
# _____________________________________________________________________________________________
print('正在访问链接{url}...'.format(url = url))
md5_value = self.get_urls_md5_hash(url)
filename = '{md5}.html'.format(md5=md5_value)
if os.path.exists(filename):
with open(filename,'rb') as f:
return f.read()
else:
# 在网站爬取错误的时候输入错误信息,然后继续
try:
html = request.urlopen(url).read()
except:
html = None
print('读取网页失败:{html}'.format(html=html))
if html:
with open(filename,'wb') as f:
f.write(html)
return html
else:
return html
本项目还采用集合来判断链接之前是不是已经加入added_urls
了,集合查找的速度比较快。
if url not in self.set:
self.added_urls.append([url,url_index])
self.set.add(url)
全部代码:
from urllib import request
from bs4 import BeautifulSoup
import hashlib
import re
import os
import time
# 队列用来层序遍历这棵树
# 集合用来检查链接是否已经放入队列过,set查找的速度比较快
LIKASHING = '/wiki/Li_Ka-shing'
JAYCHOU = '/wiki/Jay_Chou'
# 时间测量的装饰器
def timing(f):
def wrap(*args):
time1 = time.time()
ret = f(*args)
time2 = time.time()
print('%s function took %0.3f s' % (f.__name__, (time2-time1)))
return ret
return wrap
class Spider(object):
def __init__(self,start_url,end_url):
self.start_url = start_url
self.end_url = end_url
self.added_urls = [[start_url,-1],]
self.set = set()
self.set.add(start_url)
def checkurl(self,url):
if url.startswith('https'):
return url
else:
return 'https://en.wikipedia.org' + url
def get_urls_md5_hash(self,url):
# 根据urls计算哈希值作为文件名
md5 = hashlib.md5()
md5.update(url.encode())
return md5.hexdigest()
# 第一次从网络获取并保存到硬盘,第二次从硬盘读取
def get_urls_html(self,url):
# 检查链接是否加入了'https://en.wikipedia.org/'
url = self.checkurl(url)
# _____________________________________________________________________________________________
print('正在访问链接{url}...'.format(url = url))
md5_value = self.get_urls_md5_hash(url)
filename = '{md5}.html'.format(md5=md5_value)
if os.path.exists(filename):
with open(filename,'rb') as f:
return f.read()
else:
# 在网站爬取错误的时候输入错误信息,然后继续
try:
html = request.urlopen(url).read()
except:
html = None
print('读取网页失败:{html}'.format(html=html))
if html:
with open(filename,'wb') as f:
f.write(html)
return html
else:
return html
@timing
def get_all_wiki_urls(self,url):
html = self.get_urls_html(url)
bs_obj = BeautifulSoup(html,'lxml')
body_content = bs_obj.find('div',id='bodyContent')
if not body_content:
print('网页{url}找不到bodyContent'.format(url=url))
return None
all_a = body_content.findAll('a',href=re.compile('^(/wiki/)((?!:).)*$'))
retlist = []
for a_tag in all_a:
if 'href' in a_tag.attrs:
re.match(r'^(/wiki/)',a_tag['href'])
retlist.append(a_tag['href'])
return retlist
# 广度优先遍历wiki
def find_wiki_degree_bs(self):
url_index = 0
while url_index < len(self.added_urls):
# _____________________________________________________________________________________________
print('当前的url_index{url_index},元素个数{length}'.format(url_index = url_index,length = len(self.added_urls)))
url_and_father = self.added_urls[url_index]
urls = self.get_all_wiki_urls(url_and_father[0])
if self.end_url in urls:
print('在{url}找到目标链接了!!!'.format(url = url_and_father[0] ))
result = [self.end_url,url_and_father[0]]
temp_father_index = url_and_father[1]
while temp_father_index >= 0:
url_and_father = self.added_urls[temp_father_index]
result.append(url_and_father[0])
temp_father_index = url_and_father[1]
return result
else:
for url in urls:
if url not in self.set:
self.added_urls.append([url,url_index])
self.set.add(url)
url_index += 1
# we can't find until queue is empty
return None
def main():
myspider = Spider(LIKASHING,JAYCHOU)
result = myspider.find_wiki_degree_bs()
print(result)
if __name__ == '__main__':
main()
项目后序改进:
1.还可以用深度遍历进行遍历,但是求不出最短的路径,找到有很大的随机性
2.异步实现网络加载,这样子下载网页就会快很多。
3.多进程解析网页和保存网页等等加快程序执行速度。
4.自己实现布隆过滤器来过滤网站减少内存的占用,现在是采用python的集合来存储和判断某个链接是否已经存在,占用内存很大