遍历下载网站群————link_crawler
能够想到的方法有:1.通过网站地图 2.通过网站的url特点3.像普通用户一样追踪链接。
由于1、2两种方法比较简单,并且有很大的局限性,所以着重讲第三种方法,也是应用面更广的
方法。
实例:link_crawler('http://example.webscraping.com','/(index|view)')
1.版本1.0
import re
def link_crawler(seed_url):
crawl_queue=[seed_url]
while crawl_queue:
url=crawl_queue.pop()
html=download(url)#笔记三中的函数
crawl_queue.append(get_link(html))
def get_link(html):
webpage_patt=re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
return webpage_patt.findall(html.decode())#返回一个包含所以页面link的列表,由于匹配模式是str类型,而html是bytes类型。所以要把html进行decode()解码。--默认为utf-8来解码
2.按一定规则搜集同类型的页面,版本2.0
import re
def link_crawler(seed_url,link_res):
crawl_queue=[seed_url]
while crawl_queue:
url=crawl_queue.pop()
html=download(url)
#加入一个过滤器
for link in get_link(html):
if re.match(link_res,link):
crawl_queue.append(link)
def get_link(html):
webpage_patt=re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
return webpage_patt.findall(html.decode())#返回一个包含所以页面link的列表
3.发现get_link的网站是相对链接,并不是绝对链接所以要拼接,用urllib.parse
版本3.0
import re
from urllib import parse
def link_crawler(seed_url,link_res):
crawl_queue=[seed_url]
while crawl_queue:
url=crawl_queue.pop()
html=download(url)
#加入一个过滤器
for link in get_link(html):
if re.match(link_res,link):
link=parse.urljoin(seed_url,link)
crawl_queue.append(link)
def get_link(html):
webpage_patt=re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
return webpage_patt.findall(html.decode())#返回一个包含所以页面link的列表
4.发现有的网站之间相互存在链接,为避免重复抓取。
版本4.0:
import re
from urllib import parse
def link_crawler(seed_url,link_res):
crawl_queue=[seed_url]
seen=set(crawl_queue)
'''
这里的前两行是初始赋值的作用,后面的循环中
就不好赋值了,特别是在循环中很难操作set()
使其增加
'''
while crawl_queue:
url=crawl_queue.pop()
html=download(url)
#加入一个过滤器#在过滤器中看是否重复
for link in get_link(html):
if re.match(link_res,link):
link=parse.urljoin(seed_url,link)
if link not in seen:#先筛选符合条件的link,再进行筛选是否看过,这个顺序能减少工作量。
crawl_queue.append(link)
seen.add(link)
def get_link(html):
webpage_patt=re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
return webpage_patt.findall(html.decode())#返回一个包含所以页面link的列表
5.为了防止爬虫被禁,运用urllib.robotparser模块分析robots.txt
版本5.0
import re
from urllib import parse
from urllib import robotparser
def link_crawler(seed_url,link_res,User_agent='wswp'):
crawl_queue=[seed_url]
seen=set(crawl_queue)
#读取robots.txt
rp=robotparser.RobotFileParser()
rp.set_url('http://example.webscraping.com/robots.txt')
rp.read()
'''
这里的前几行是初始赋值的作用,后面的循环中
就不再需要赋值了,特别是在循环中很难操作set()
使其增加
'''
while crawl_queue:
url=crawl_queue.pop()
#检查该url是否能被禁止爬取
if rp.can_fetch(User_agent,url):
html=download(url)
#加入一个过滤器#在过滤器中看是否重复
for link in get_link(html):
if re.match(link_res,link):
link=parse.urljoin(seed_url,link)
if link not in seen:#先筛选符合条件的link,再进行筛选是否看过,这个顺序能减少工作量。
crawl_queue.append(link)
seen.add(link)
else:
print('Blocked by robots.txt',url)
def get_link(html):
webpage_patt=re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
return webpage_patt.findall(html.decode())#返回一个包含所以页面link的列表
6.下载限速,为了防止爬虫速度过快,遭到禁封和造成服务器过载的风险。需要在两次同一主站网站下载之间设置延迟:
这时直接加入程序到link_crawler函数中:
应该实现:1.要能记录两次下载时间的功能,计算是否需要延迟,以及延时多久。
版本6.0
import re
from urllib import parse
from urllib import robotparser
import time
def link_crawler(seed_url,link_res,User_agent='wswp'):
crawl_queue=[seed_url]
seen=set(crawl_queue)
#读取robots.txt
rp=robotparser.RobotFileParser()
rp.set_url('http://example.webscraping.com/robots.txt')
rp.read()
'''
这里的前几行是初始赋值的作用,后面的循环中
就不再需要赋值了,特别是在循环中很难操作set()
使其增加
'''
b=0
delay=3
while crawl_queue:
url=crawl_queue.pop()
#检查该url是否能被禁止爬取
if rp.can_fetch(User_agent,url):
#时间管理器
a=time.time()
if delay:
sleeptime=delay-(a-b)
if sleeptime>0:
time.sleep(sleeptime)
html=download(url)
b=time.time()
#加入一个过滤器#在过滤器中看是否重复
for link in get_link(html):
if re.match(link_res,link):
link=parse.urljoin(seed_url,link)
if link not in seen:#先筛选符合条件的link,再进行筛选是否看过,这个顺序能减少工作量。
crawl_queue.append(link)
seen.add(link)
else:
print('Blocked by robots.txt',url)
但是,在访问有外链的网站时,比如hao123的网址有百度的链接。就不需要暂停。
所以还要实现以下功能:
1.要能记录两次下载时间的功能,计算是否需要延迟,以及延时多久。
2.要能记录不同主站的下载时间。————需要创建一个字典来建立对应关系。
eg:hao123,下的网址download完了,可以立刻下载baidu下的网址,但是要隔3秒才能下载hao123下的网址。
版本6.1
import time
import re
from urllib import parse
from urllib import robotparser
def link_crawler(seed_url,link_res,User_agent='wswp'):
crawl_queue=[seed_url]
seen=set(crawl_queue)
#读取robots.txt
rp=robotparser.RobotFileParser()
rp.set_url('http://example.webscraping.com/robots.txt')
rp.read()
#时间管理器初始化
#b=0
last_time={}
delay=3
'''
这里的前几行是初始赋值的作用,后面的循环中
就不再需要赋值了,特别是在循环中很难操作set()
使其增加
'''
while crawl_queue:
url=crawl_queue.pop()
#检查该url是否能被禁止爬取
if rp.can_fetch(User_agent,url):
#时间管理器
#记录下载的是哪个网站
net_loc=urlparse(url).netloc
#a=time.time()
if delay:
#sleeptime=delay-(time.time()-last_time[net_loc])#初始化的原因,一开始是空字典,这么做会出错。需要用到字典取值的另外的方法:.get(key),key存在返回value。不存在返回None
if last_time.get(net_loc) is not None:
sleeptime=delay-(time.time()-last_time[net_loc])
if sleeptime>0:
time.sleep(sleeptime)
html=download(url)
last_time[net_loc]=time.time()
#b=time.time()
#加入一个过滤器#在过滤器中看是否重复
for link in get_link(html):
if re.match(link_res,link):
link=parse.urljoin(seed_url,link)
if link not in seen:#先筛选符合条件的link,再进行筛选是否看过,这个顺序能减少工作量。
crawl_queue.append(link)
seen.add(link)
else:
print('Blocked by robots.txt',url)
虽然,至此已经解决了下载限速的问题。但是把这种有特点功能的程序都写在一起并不好,首先代码的可读性降低,其次等要修改时间管理器的时候还要找在什么地方要修改。
为了让程序有更好的扩展性,可以把它模块化。在程序设计越来越复杂的时候需要把一定功能的程序抽象为模块(面向对象的设计),让程序更加可控。
所以建立一个类,其作用就是时间管理器。
版本6.2
import re
from urllib import parse
from urllib import robotparser
class Timedelay:
#初始化
def __init__(self,delay):
#设置延迟时间
self.delay=delay
#创建记录主站的字典
self.domains={}
#创建等待函数,同时还要实现记录走后一次访问时间
def wait(self,url):
netloc=urlparse(url).netloc
last_time=self.domains.get(netloc)
if self.delay and last_time:
sleeptime=self.delay-(time.time()-last_time)
if sleeptime>0:
time.sleep(sleeptime)
#每次暂停后,或者没暂停都重置最后一次访问时间
self.domains[netloc]=time.time()
def link_crawler(seed_url,link_res,User_agent='wswp',delay=None,proxy=None):
crawl_queue=[seed_url]
seen=set(crawl_queue)
#读取robots.txt
rp=robotparser.RobotFileParser()
rp.set_url('http://example.webscraping.com/robots.txt')
rp.read()
timedelay=Timedelay(delay)#同样是初始化
'''
这里的前几行是初始赋值的作用,后面的循环中
就不再需要赋值了,特别是在循环中很难操作set()
使其增加
'''
while crawl_queue:
url=crawl_queue.pop()
#检查该url是否能被禁止爬取
if rp.can_fetch(User_agent,url):
timedelay.wait(url)#暂停,并记下本次主站下载开始时间
html=download(url,proxy=proxy)
#加入一个过滤器#在过滤器中看是否重复
for link in get_link(html):
if re.match(link_res,link):
link=parse.urljoin(seed_url,link)
if link not in seen:#先筛选符合条件的link,再进行筛选是否看过,这个顺序能减少工作量。
crawl_queue.append(link)
seen.add(link)
else:
print('Blocked by robots.txt',url)
7.爬虫陷阱。目前爬虫会跟踪以前从没有访问过的页面。但是有的网站动态生成页面内容,这样会出现无限多的网页。这样爬虫会无限制链接下去。
为了避免爬虫陷阱,简单的方法是到达当前页面经过了多少链接,也就是深度。
功能:当达到这个深度的时候就不往队列里添加链接了,修改seen,不仅可以记录,而且还有对应的深度记录。改为字典(判别重复的概率低了)
def link_crawler(seed_url,link_res,User_agent='wswp',delay=None,proxy=None,maxdepth=2):
crawl_queue=[seed_url]
#seen=set(crawl_queue)
seen={}
seen[seed_url]=0
#读取robots.txt
rp=robotparser.RobotFileParser()
rp.set_url('http://example.webscraping.com/robots.txt')
rp.read()
timedelay=Timedelay(delay)#同样是初始化
'''
这里的前几行是初始赋值的作用,后面的循环中
就不再需要赋值了,特别是在循环中很难操作set()
使其增加
'''
while crawl_queue:
url=crawl_queue.pop()
#检查该url是否能被禁止爬取
if rp.can_fetch(User_agent,url):
timedelay.wait(url)#暂停,并记下本次主站下载开始时间
html=download(url,proxy=proxy)
#加入一个过滤器#在过滤器中看是否重复
dept=seen[url]
if dept!=maxdepth:
for link in get_link(html):
if re.match(link_res,link):
link=parse.urljoin(seed_url,link)
if link not in seen:#先筛选符合条件的link,再进行筛选是否看过,这个顺序能减少工作量。
crawl_queue.append(link)
seen[link]=dept+1
print(seen)
else:
print('Blocked by robots.txt',url)