爬虫学习笔记(四)——遍历下载网站群link_crawler

遍历下载网站群————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(']+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(']+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(']+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(']+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(']+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)  

你可能感兴趣的:(爬虫学习笔记(四)——遍历下载网站群link_crawler)