爬虫小示例

#!Python3

#-*- coding: utf-8 -*- 

#网页爬虫示例 用于抓取的示例网址http://example.webscraping.com  ,搭建该网站的源代码 http://bitbucket.org/wswp/places 

'''

准备着手抄写爬虫程序时,想到最近看的教程中出现的一些模块urllib2,urllib,和Requests,于是搜索了一下他们的异同

详细的比较复杂,大致就是:Python2中urllib2为主,urllib为辅,Python3中urllib2和urllib就合并为urllib啦。但是呢,urllib的用法还是很麻烦,requests库的用法更Python一些。requests的功能好像能替换urllib的功能吧,以后发现了再说。

'''

#用户代理:服务器用于标识请求的浏览器身份的变量

#p36 原始代码地址 https://bitbucket.org/wswp/code/src/tip/chapter02/link_crawler.py

'''

该脚本主要实现根据一个种子URL,下载 【从这个种子URL起  每个下载页面中包含的】  所有符合要求的URL,但并未保存下载的URL。

符合的要求包含: (1)user_agent是否被允许下载这个URL (2)该URL是否满足用户设置的正则表达式 (3)该URL是否达到限制的爬取深度(4)下载的URL是否达到设置的最大限制数

内部预防隐患的功能点有:(1)避免下载过于频繁导致的封号设置了同域名的下载时间间隔(2)避免某些用户代理被禁止请求设置了用户代理(3)避免爬取某用户代理不被允许爬取的页面解析了robots.txt文件规则做判断(4)避免在一个页面中进入无休止的链接爬取设置了最大爬取深度,(5)避免重复爬取已下载的URL设置了记录所有URL的变量(6)为合理控制下载规模设置了最大下载数(7)避免页面中的URL为相对链接将其转化为绝对URL(8)避免由于服务器端响应缓慢、响应繁忙等原因导致下载失败设置了重试下载次数(9)避免一个URL的下载错误使程序终止使用了try...except 跳过

'''

import re

from urllib import parse

import urllib

import time

from datetime import datetime

from urllib import robotparser

from collections import deque  #deque一种实现高效删除和插入的双向列表,在多线程中可同时在两端进行操作。

def link_crawler(seed_url, link_regex=None, delay=5, max_depth=10, max_urls=100, headers=None, user_agent='wswp', proxy=None, num_retries=1):

    """

    爬取符合正则表达式限制的格式的URL地址:

 根据传入的参数配置URL下载的相关信息(下载时间间隔,robots条例,用户代理)

 进入下载循环{

        1.下载(取待下载列表尾端的URL,根据robots判断是否能下载,是否需延时下载,然后下载),

 2.添加新URL(获取当前下载页面深度,没达到深度限制,解析出当前下载页面中包含满足正则的URL,标准格式化                             URL,将没遇到过的URL添加进记录所有遇到过的URL的列表,记录这些子URL的深度(父URL的深度+1),将与种子URL相同域名的子URL           加入待下载列表),

 3.记录下载URL的数(num_urls += 1)并判断是否达到最大下载数,是否进入下一次循环

                   }

    """

    # 记录待爬取的URL地址,已爬取的URL被删除

    crawl_queue = deque([seed_url])

    # 记录遇到过的满足正则表达式的URL地址,和这个URL地址的深度,用于去重和判断深度

    seen = {seed_url: 0}

    # 记录被下载的URL数

    num_urls = 0

        rp = get_robots(seed_url)  #get_robots() 自定义函数 返回一个已解析的URL的robots.txt的内容对象

    throttle = Throttle(delay) #创建下载频率调控类实例

    headers = headers or {}

    if user_agent:

        headers['User-agent'] = user_agent

    while crawl_queue:

        url = crawl_queue.pop()           #删除待爬取队列尾端的元素,并返回这个元素的值

                if rp.can_fetch(user_agent, url): # 只通过种子URL页面的robots.txt 判断这个 用户代理 是否被允许下载这个URL

            throttle.wait(url)            #能下载就使相同域名的URL下载时间间隔 达到 设置的秒数

            html = download(url, headers, proxy=proxy, num_retries=num_retries)  #下载页面 返回下载页面的html文本

            #记录当前页面中所有满足正则表达式的URL

            links = []

            #获取当前下载页面的深度

            depth = seen[url]

   #if下的语句用于添加  当前下载  的页面中   满足正则表达式和深度要求的   URL ,添加到crawl_queue队列中

            if depth != max_depth: #从原始页面,到当前页面,经过了多少个链接,也就是深度。

                if link_regex: #如果设置了URL的格式标准(正则表达式),提取当前下载的页面中满足正则表达式的URL,并添加到待抓取的URL列表中

                    links.extend(link for link in get_links(html) if re.match(link_regex, link)) #extend() 函数用于在列表末尾一次性追加另一个序列中的多个值

                                    for link in links:

                    link = normalize(seed_url, link)  #标准化URL格式,去掉后面的fragment ,使URL变为绝对URL

                    # 当这个连接没有被放入seen队列时,把这个连接加入seen队列中,并记录它的深度。

                    if link not in seen:  #无重复URL才会被放到 下载队列crawl_queue当中

                        seen[link] = depth + 1 #记录 子URL的深度

                        if same_domain(seed_url, link):

                            crawl_queue.append(link)

            # 记录已下载页面数

            num_urls += 1

            if num_urls == max_urls:

                break

        else:

            print('Blocked by robots.txt:', url)

class Throttle:

    """

    控制同一个域名的下载时间间隔:

 类实例设置了统一的下载时间间隔and记载了已下载的URL域名的上次下载时间),方法wait针对单个URL使其满足同域名的下载时间间隔,

 使用该功能时,先创建类实例(传入间隔的时间),再根据某个URL调用类实例的wait(url)方法,实现针对该URL的下载时间间隔调控

 wait内在逻辑为,该URL的域名上次运行时间到当前时间不足间隔时间时,暂停直到满足时间间隔后,将当前时间作为该域名的value值

    """

    def __init__(self, delay):

        self.delay = delay

        #存储 域名(key):上次下载时间(value)  的数据容器(字典)

        self.domains = {}

            def wait(self, url):

        domain = parse.urlparse(url).netloc  #解析URL然后获得它的netloc(域名)部分

        last_accessed = self.domains.get(domain)

        if self.delay > 0 and last_accessed is not None:

            sleep_secs = self.delay - (datetime.now() - last_accessed).seconds

            if sleep_secs > 0:

                time.sleep(sleep_secs)  #暂停使时间间隔满足

        self.domains[domain] = datetime.now() #存储当前时间为该域名的value值。

        

def download(url, headers, proxy, num_retries, data=None):

    '''

    返回下载页面的html文本

 可以处理代理,传输数据,头文件,并检测由于服务器问题下载失败时在规定次数内再次下载。

    '''

    print('Downloading:', url)

    request = urllib.request.Request(url, data, headers)

    opener = urllib.request.build_opener()

    if proxy:

        proxy_params = {parse.urlparse(url).scheme: proxy} #获取URL中 的协议作为 key, proxy是一个url?

        opener.add_handler(urllib.request.ProxyHandler(proxy_params))

    try:

        response = opener.open(request)

        html = response.read()

        code = response.code

    except urllib.error.URLError as e:

        print('Download error:', e.reason)

        html = ''

        if hasattr(e, 'code'):

            code = e.code

            if num_retries > 0 and 500 <= code < 600:  #如果是服务器端的报错,并且连续出错次数小于设定值,就继续尝试。

                # retry 5XX HTTP errors

                return download(url, headers, proxy, num_retries-1, data)

        else:

            code = None

    try:

        html=html.decode('utf-8') 

    except: 

        pass  

    return html 

def normalize(seed_url, link):

    """标准化URL格式,去掉后面的fragment ,如果是相对URL,则转化为绝对URL

    """

    link, _ = parse.urldefrag(link) #将URL中的fragment和前面的URL分开 

    return parse.urljoin(seed_url, link) #避免link是相对路径,把其转换为绝对路径。

def same_domain(url1, url2):

    """

 判断两个URL是否是同一个域名

    """

    return parse.urlparse(url1).netloc == parse.urlparse(url2).netloc

def get_robots(url):

    """

 初始化robots文件,返回一个已解析的URL的robots.txt的内容对象

    """

    rp = robotparser.RobotFileParser() #创建类实例 该class提供读取、解析和回答关于对应url的robots.txt问题的方法

    rp.set_url(parse.urljoin(url, '/robots.txt'))  #set_url(url) 设置与robots.txt相关联的URL,  parse.urljoin(base,url,allow_fragments=True)合并路径。

    rp.read()    #读取robots.txt中的URL 并把它传递给parser

    return rp    

    #rp.can_fetch(useragent,url) 如果根据被解析的robots.txt文件中的规则,该userAgent被允许爬取这个URL,则返回TRUE。如果不被允许,仍然爬取,就可能被封号。

        

def get_links(html):

    """

 查找html文件中的<a标签下的href属性值,以列表形式返回。

    """

    # a regular expression to extract all links from the webpage

    webpage_regex = re.compile('<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE) #忽略大小写

    # list of all links from the webpage

    return webpage_regex.findall(html)  #返回html文本中所有<a元素下href属性的值

import time 

if __name__ == '__main__':

    user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11'

    link_crawler('http://example.webscraping.com', r'/places/default/(index|view)', delay=0, num_retries=1, user_agent='BadCrawler')

    time.sleep(5)

    link_crawler('http://example.webscraping.com/index', r'(.*?)/(index|view)', delay=0, num_retries=1, max_depth=1, user_agent=user_agent)

猜你喜欢

转载自www.cnblogs.com/Ting-light/p/9548158.html