很多语言都可以实现爬虫,由于python的易用性,这里使用python.
- 程序功能:爬取一个域名下的所有网页,并将网页之间的指向关系存储在字典中。可以通过domain 设置域名、可以通过depth设置深度。
- 程序原理:1. 使用urllib.request.urlopen 打开网页,使用BeautifulSoup解析打开的网页;2. 使用BeautifulSoup.find功能找到网页中的链接,然后将链接存入List中;3.转到步骤1,继续打开网页,重复depth次。
- 程序要点:1. (非常重要)打开网页要设置时间间隔,不能打开一个后马上下一个,否则一方面可能会被网站拉入黑名单,从而拒绝你的网页请求,另一方面会造成网站服务器的压力,造成不必要的麻烦;2. 记录网页爬行的路径,避免重复爬行,这个可以通过字典、数据库等好多方式实现;3要分清爬行到的记录是否为网站内部网页,经常出现的一种情况是爬着爬着就不知道爬到哪里去了,大型网站平均每个网页都会有好几千个链接,如果不设定爬行边界,那爬行的网页数量将呈指数级增长,这是我们不希望看到的;4. 虽然有很多链接看起来不一样,但实际上二者是同一个网页,比如说网站的内容页:http://www.81.cn/jmywyl/2018-07/06/content_8081243.htm、http://www.81.cn/jmywyl/2018-07/06/content_8081840.htm、http://www.81.cn/jmywyl/2018-07/06/content_8081240.htm ,这些链接看起来是不同的,但实际上他们采用了同一个模板自动生成,如果想将他们记为一类,则需要下点功夫进行归类了。
下面的程序只实现了程序要点中的1、2、3。实现方法都是最简单、基础的方法。至于如何实现4,还在进一步思考中,将在下一篇中更新。
欢迎留言探讨更深一步的优化
代码实现主要包括以下几个部分:
- [x] 定义一个类,包装整个爬取的程序 UrlParser
- [x] 定义一个根据当前页面搜索包含链接列表的方法 Parser
- [x] 定义一个可以存储链接之间相互联系的字典,并设置添加节点的方法 addNode
- [x] 定义一个可以判断链接是否为内部链接的方法 isInnerLink
- [x] 定义一个存储爬取结果的方法 saveGraph
代码如下:
# -*- coding: utf-8 -*-
"""
# -*- coding: utf-8 -*-
"""
Spyder Editor
This is a temporary script file.
"""
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
import re
import logging
class UrlParser:
def __init__(self,domain,depth,domainName):
self.domain = domain
print(self.domain)
self.depth = depth
self.domainName = domainName
self.LinkGraph= {}
self.LinkGraph[self.domain] = self.domain
logging.basicConfig(filename='D://example.txt',level=logging.DEBUG)
logging.info('=====================starting==========')
#使用字典存储网页之间的连接关系
def addNode(self, parent,children):
if(self.LinkGraph.get(children)!=None):
return False
else:
self.LinkGraph[children] = parent
return True
#判断一个链接是否为网站内部的链接
def isInnerLink(self,url):
# url 以http开头 则判断是否在同一个domain下
# url 不以http开头 则将url 补全
if(url==None):
return ''
if(url.startswith('http')):
if(re.match(r'^https*://\w*\.'+self.domainName+'\.',url)!=None):
return url
else:
return ''
else:
if(url.startswith('//')):
url = url[2:]
if(re.match(r'\w*\.'+self.domainName+'\.',url)!=None):
return 'http://'+url
else:
return ''
elif(url.startswith('/')):
return self.domain+url
else:
if(re.match(r'\w*\.'+self.domainName+'\.',url)!=None):
return 'http://'+url
else:
return ''
#依次访问LinkListpre中的链接指向的网页,并分析网页,得到网页中的链接
def Parser(self,LinkListpre,LinkListnext):
for url in LinkListpre:
print(url)
try:
html = urlopen(url)
except BaseException as e:
logging.error('Parser::::warmning: url cannot open:::'+url)
continue;
try:
bsObj = BeautifulSoup(html.read(),'lxml')
for link in bsObj.find_all('a'):
#print(link)
try:
link = link['href']
except KeyError as e:
continue
#是内部链接
link = self.isInnerLink(link)
#长度超过2 是内部链接 且没有访问过
if ((len(link.rstrip())>2)):
if(self.addNode(url,link)):
LinkListnext.append(link)
except BaseException as e:
logging.error('Parser::::warmning: page cannot be parsed:::'+url)
#模拟人类点击习惯,设置随机间隔
time.sleep(1+random.random())
return True
def saveGraph(self):
Gra = pd.DataFrame({'next':self.LinkGraph})
Gra.to_csv('project1/test.csv')
def Go(self):
LinkListpre = [self.domain]
LinkListnext = []
for i in range(self.depth):
print('====================')
self.Parser(LinkListpre,LinkListnext)
print(len(LinkListnext))
LinkListpre = LinkListnext
LinkListnext = []
if(len(LinkListpre)<1):
print('*********')
break
self.saveGraph()
MyParser = UrlParser('https://XXX.mycooper.com',2,'mycooper')
MyParser.Go()