需求
事情是这样的,最近需要把一个域名下面的所有404的页面链接给抓出来,因为这个域名下面的链接实在太多了,人工的方法肯定不行,网上也没有找到什么现成的工具,但是我是谁啊,脚本小能手啊,那就自己用python撸一个咯
深度优先遍历
一开始是这么想的,拿到根路径之后先把所有的子路径拿出来,分别用urllib去验证这些路径,如果验证不通过则记录下来,否则则用该子路径作为根路径去遍历他的子路径,废话不多说直接上代码
# 递归 深度优先遍历
import urllib.request
from urllib.request import URLError
import http.cookiejar
from selenium import webdriver
passlist = {
}
path = {
}
def getCurrent(urlRoot):
try:
if urlRoot in path:
return
driver.get(urlRoot) # 要测试的页面
path[urlRoot] = 1
# try:
# devloplist = driver.find_elements_by_id("in-the-process-of-development")
# if(len(devloplist)!=0):
# devlopPath = open("devlopPath.txt","a+")
# devlopPath.write(urlRoot+"\n")
# devlopPath.close()
# except:
# print("not devlopment")
finally:
#urlRoot = urlRoot.split('#')
urls = driver.find_elements_by_xpath("//a") # 匹配出所有a元素里的链接
us = []
for url in urls:
us.append(url.get_attribute('href'))
for u in us:
if u == 'None': # 很多的a元素没有链接,所有是None
continue
if u.find("https://dynamsoft.github.io/barcode-sdk-docs-dev")==-1:#控制域名防止跳转到其他站点
continue
if urlRoot+","+u in passlist:
continue
try:
response=urllib.request.urlopen(u) # 可以通过urllib测试url地址是否能打开
except:
passlist[urlRoot+","+u]=1
errorPath = open("errorPath.txt","a+") #记录不成功的路径
errorPath.write(urlRoot+","+u+"\n") #父路径:子路径的格式保存
errorPath.close()
print('Error url: '+urlRoot+","+ u) # 把测试不通过的url显示出来
continue
else:
passlist[urlRoot+","+u]=1
print('Success url: ' + u) # 测试通过的url展示出来
getCurrent(u)
except:
return
option=webdriver.ChromeOptions()
#option.add_argument("headless")
driver = webdriver.Chrome('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chromedriver.exe')
getCurrent("https://dynamsoft.github.io/barcode-sdk-docs-dev")
driver.close()
理想是美好的,但是现实总是不如人意,我挂着爬虫第二天起来一看,卧槽,崩溃了!
可以我明明在最外层加上try catch了啊,所以是不会有异常导致的崩溃的,于是我看了一眼errorPath.txt这个文件,效果还是有的,找到了很多的404路径
可以看出这个域名下面的链接非常的多,于是我把option.add_argument(“headless”)这行代码注释掉了,模拟了一下这个爬虫爬的路径。
害,怪不得呢,这个爬虫逻辑是没什么问题,问题就出在他是个递归,递归是通过函数调用栈来模拟栈的,每次递归都会压栈,资源是有限的,压着压着就炸了呗
这里有两种解决办法
- 方法一
通过python的栈来存地址路径,简单点来说就是改递归为循环通过stack的数据结构来实现 - 方法二
采取宽度优先遍历,通过队列来实现遍历,每次拿出队头的路径,然后将验证通过的路径加入队列中,把验证不通过的路径记录下来
深度优先遍历
这里我用了第二种方法,不说废话,上代码
#宽度优先遍历
import urllib.request
from urllib.request import URLError
import http.cookiejar
from selenium import webdriver
import queue
option=webdriver.ChromeOptions()
option.add_argument("headless")
faillist = {
}
passlist = {
}
q = queue.Queue()
q.put("https://dynamsoft.github.io/barcode-sdk-docs-dev")
driver = webdriver.Chrome('C:\\Program Files (x86)\\Google\\Chrome\\Application\\chromedriver.exe',chrome_options = option)
while(not q.empty()):
try:
print(q.qsize())
url = q.get()
driver.get(url)
urls = driver.find_elements_by_xpath("//a") # 匹配出所有a元素里的链接
us = []
for data in urls:
us.append(data.get_attribute('href'))
for i in range(len(us)):
u = us[i]
if u.find("https://dynamsoft.github.io/barcode-sdk-docs-dev")==-1:
continue
if u in passlist:
#print(u+"已经遍历过")
if passlist[u]==0:# 失败
errorPath = open("errorPath.txt","a+")
errorPath.write(url+","+u+",from fail dict\n")
errorPath.close()
else:# 通过的
continue
else:
#print(u+"没有遍历过")
try:
response=urllib.request.urlopen(u)# 可以通过urllib测试url地址是否能打开
except Exception as e:
print(e)
errorPath = open("errorPath.txt","a+")
errorPath.write(url+","+u+","+str(e)+"\n")
errorPath.close()
#print('Error url: '+url+","+ u) # 把测试不通过的url显示出来
passlist[u]=0
continue
else:
#print('Success url: ' + u) # 测试通过的url展示出来
passlist[u]=1
q.put(u)
except:
continue
反正就是把地址记录下来,把函数调用栈的资源改成内存资源,这样就不会崩溃啦,q.qsize()输出为1的时候就是遍历结束的时候!
讲道理,我觉得有点深度优先和宽度优先遍历的意思在里头了。。。
哦对了,还有一点,urllib测试不通过的链接不一定全是404链接,还有一部分是因为你一直去访问这个站点,会被人家当成是在攻击人家,这种在计算机安全里面好像叫做拒绝服务攻击,英文叫DoS。
总之不是所有爬下来的链接都是404你可以在except Exception as e:里面继续改一下如果 e是<urlopen error [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond>的话就passlist[u]=1咯
anyway, 我实现写的烂了点,但是我懒得改了,很可怕了~如果你们需要404链接验证爬虫就将就着用吧。。。