在使用爬虫爬取多个页面时(比如爬取邮箱,手机号等),一般层级越高与我们原始目标数据之间准确率越低,所以很有必要控制爬取层级达到有效爬取
无论是深度还是广度爬取,都需要以下变量和方法
#链接的正则表达式,注意是在标签中的href属性里的才是真正的链接
PATTERN_URl = "<a.*href=\"(https?://.*?)[\"|\'].*"
#获取网页源代码,注意使用requests时访问https会有SSL验证,需要在get方法时关闭验证
def getHtml(url):
res = requests.get(url,verify=False)
text = res.text
return text
#有时还是会有警告,可以采用以下方式禁用警告
#import urllib3
#urllib3.disable_warnings()
#获取指定页面中含有的url
def getPageUrl(url,html=None):
if html == None:
html = getHtml(url)
uList = re.findall(PATTERN_URl, html)
return uList
深度:从首页爬取到所有url中,进入到第一个url(第1级)中再爬取所有url,进入第2级中的第一个url中继续操作,所以应该通过递归控制层级。
#深度字典,{url:层级,...},用来去重
depthDict = {}
def getUrlsDeep(url,depth = 3):
try:
if(depthDict[url]>=depth):
return
#避免碰到了下载链接,可见下文
# if 'download' in str(url):
# return
#获取此页中的所有连接
clist = getPageUrl(url)
print("\t\t"*depthDict[url],"#%d:%s"%(depthDict[url],url))
for c in clist:
#判断深度字典有有无此键,达到去重目的
if c not in depthDict:
depthDict[c]=depthDict[url]+1
getUrlsDeep(c)
except Exception as e:
pass
if __name__ == '__main__':
startUrl = 'https://gsh.cdsy.xyz'
#爬取页面设置有第0级
depthDict[startUrl] = 0
#一共爬取2级
getUrlsDeep(startUrl,depth=3)
关于防止爬到下载链接从而一直在下载,对于html页面类型Content Type的内容为"text/html",可以获取页面消息头信息来实现。
由于requests模块中的headers属性是要get完响应页面才能获取到,也就是说要等下载完才能知道是否为下载链接。所以可以使用urllib.request来获取消息头,参考https://www.cdsy.xyz/computer/programme/Python/230823/cd45622.html
运行结果
...
广度:在首页中爬取所有url,然后进入第1级中的第1个url中爬取url,再进入第1级中第2个页面爬取url。可以将未爬取的页面存放在一个临时列表中,每爬取一个页面则出队,将爬取到的进队,所以是通过循环控制层级
#临时存放url的列表,作为待爬列表,通过队列FIFO特性遍历url
tmplist = []
#存放爬取到的所有url
urlList = []
#同为深度字典
depthDict = {}
def getUrls(depth):
while (len(tmplist)>0):
#从临时列表中移除并获取对首元素链接
url = tmplist.pop(0)
#添加到链接列表
urlList.append(url)
if depthDict[url] < depth:
#获取子页面url列表
subList = getPageUrl(url)
for u in subList:
if u not in depthDict:
depthDict[u] = depthDict[url]+1
tmplist.append(u)
if __name__ == '__main__':
startUrl = 'https://gsh.cdsy.xyz'
depthDict[startUrl] = 0
tmplist.append(startUrl)
getUrls(3)
for url in urlList:
print("\t"*depthDict[url],"#%d:%s"%(depthDict[url],url))
运行结果
···
...
分析两者方式运行结果,深度与广度之间的逻辑区别清晰可见:)
可以发现通过以上的单线程广度爬取速度很慢,可以改进成多进程的:
tmplist = []
depthDict = {}
urlList = []
#线程列表
tlist = []
#获取指定url中的所有url,并加入到待爬列表中,作为线程的target
def getSonPageUrl(url):
subList = getPageUrl(url)
for u in subList:
if u not in depthDict:
depthDict[u] = depthDict[url] + 1
tmplist.append(u)
def getUrls(depth):
#当还有待爬网页或者还有运行中的线程
while ((len(tmplist)>0) or (threading.activeCount()>1)):
while len(tmplist) == 0:
continue
url = tmplist.pop(0)
urlList.append(url)
#print(threading.activeCount(),"\t" * depthDict[url], "#%d:%s" % (depthDict[url], url))
if depthDict[url] <depth:
t = threading.Thread(target=getSonPageUrl,args=(url,))
tlist.append(t)
t.start()
startUrl = 'https://gsh.cdsy.xyz'
if __name__ == '__main__':
depthDict[startUrl] = 0
tmplist.append(startUrl)
getUrls(3)