python爬虫之一个完整的小爬虫

今天就直接进入主题
这个爬虫需要用到的包:
其中mongo_cache使我们自己定义的向MongoDB里存储数据的包,大家可以参照我之前的有关MongoDB的讲解https://blog.csdn.net/oyjl19961216/article/details/82778256

import requests
from fake_useragent import UserAgent  # 随机生成User-Agent
from retrying import retry  # 使用装饰器
import hashlib
import queue
import re
from urllib import robotparser
from urllib.parse import urlparse, urldefrag, urljoin
from threading import Thread
from datetime import datetime
import time
import mongo_cache

第一步我们可以使用面向对象的思想,所以先创建一个类

class CrawlerCommon(object):
    """https://www.csdn.net/
    通用下载爬虫
    """

第二步:初始函数,这里有一些函数也是自己定义的,会在下面提及到

def __init__(self, init_url):
        """
        初始化函数
        :param init_url:
        """
        __ua = UserAgent()
        # 使用fake-useragent随机生成一个访问头
        self.headers = {'User-Agent': __ua.random}
        # 设置最初的种子网址
        self.seed_url = init_url
        # 使用不同的队列会造成BFS和DFS的效果
        # 使用先进先出队列产生广度优先搜索,使用先进后出(栈)产生深度优先搜索
        self.crawler_queue = queue.Queue()
        self.crawler_queue.put(init_url)
        self.rp = get_robots(init_url)
        self.link_regex = '(index|view)'
        self.throttle = Throttle(5.0)
        # self.mcache = mongo_cache.MongoCache()
        self.visited = {init_url: 0}

第三步:定义下载函数,与重试下载函数

 def dowmload(self, url_str, data=None, method='GET', proxies={}):
        """
        真正的下载类
        代理模式
        :param url_str:
        :param data:
        :param method:
        :param proxies:
        :return:
        """
        print('download url is :::::', url_str)
        try:
            result = self.retry_download(url_str, data, method, proxies)
        except Exception as e:  # 异常处理尽量使用具体的异常
            print(e)
            result = None
        return result
@retry(stop_max_attempt_number=3)
    def retry_download(self, url_str, data, method, proxies):
        """
        通过装饰器封装重试下载模块,最多重试三次
        :param url_str: 下载网页的最终地址
        :param data: post传输数据
        :param method: 下载方法post或get
        :param proxies: 代理服务器
        :return: 下载结果
        """
        if method == 'POST':
            result = requests.post(url_str, data=data, headers=self.headers, proxies=proxies)
        else:
            result = requests.get(url_str, headers=self.headers, timeout=3, proxies=proxies)
        assert result.status_code == 200  # 使用断言判断下载状态,成功则返回结果,失败抛出异常
        return result.content

因为是完整的爬虫所以不可能只是下载一页:所以会爬取网页上的网址再次进行爬取

def nomalize(self, url_str):
        """
        补全下载链接
        实现一个类方法的时候,要注意类方法是否使用了当前类的属性或其他的方法,
        如果没有使用说明和当前类没有直接联系,最好独立出来,当做工具方法
        :param url_str:
        :return:
        """
        real_url, _ = urldefrag(url_str)  # 去掉网址后面的#
        return urljoin(self.seed_url, real_url)  # 将下载地址拼接上网站前缀

最后进行运行:

def run(self):
        """
        进行网页爬取的主要方法
        :return:
        """
        while not self.crawler_queue.empty():
            url_str = self.crawler_queue.get()
            # 检测 robots.txt文件规则
            if self.rp.can_fetch(self.headers['User-Agent'], url_str):
                self.throttle.wait_url(url_str)
                depth = self.visited[url_str]
                if depth < MAX_DEP:
                    # 下载链接
                    html_content = self.dowmload(url_str)
                    # 存储连接
                    if html_content:
                        # self.mcache[url_str] = html_content
                        save_url(html_content, url_str)
                    # 筛选出页面所有的连接
                    url_list = extractor_url_lists(html_content)
                    # 筛选出需要爬去的连接
                    filter_urls = [link for link in url_list if re.search('/(52_52542)', link)]
                    for url in filter_urls:
                        # 补全链接
                        real_url = self.nomalize(url)
                        # 判断连接是否访问过
                        if real_url not in self.visited:
                            self.visited[real_url] = depth + 1
                            self.crawler_queue.put(real_url)
            else:
                print('robots.txt 禁止下载:', url_str)

但是到这里这个爬虫到这里依然不完整,许多细心的小伙伴肯定发现了,接下来继续补全,下面这些函数是定义在类外的函数,因为他们没有用到类内的变量。
上面调用了检查robots文件的函数:

def get_robots(url):
    """
    解析robots.txt文件
    :param url:
    :return:
    """
    rp = robotparser.RobotFileParser()
    robots_url = urlparse(url).scheme + '://' + urlparse(url).netloc
    rp.set_url(urljoin(robots_url, 'robots.txt'))
    rp.read()
    # 返回Ture说明可以调用,否则返回False
    # rp.can_fetch("http://www.baidu.com",user-agent)
    return rp

抽取网页链接的函数:

def extractor_url_lists(html_content):
    """
    抽取网页中的其他链接
    :param html_content:网页内容
    :return:网页中的其他链接
    """
    # html.xpath('//a[@href]')
    url_regex = re.compile('<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE)
    return url_regex.findall(html_content.decode("gbk"))

存储下载内容的函数:

def save_url(html_content, url_str):
    """
    存储下载内容
    :param html_content:
    :param url_str:
    :return:
    """
    # md5加密
    md5 = hashlib.md5()
    md5.update(html_content)
    file_path = "./download/" + md5.hexdigest() + ".html"
    with open(file_path, 'wb') as f:
        f.write(html_content)

最后还需要一个下载限速器:

class Throttle(object):
    下载限流器

    def __init__(self, delay):
        self.domains = {}  # 可以放到MongoDB数据库中
        self.delay = delay  # 两次下载时间间隔

    def wait_url(self, url_str):
        # 以netloc为基础进行休眠
        domain_url = urlparse(url_str).netloc
        # 从字典里取出上次访问的时间
        last_accessed = self.domains.get(domain_url)
        if self.delay > 0 and last_accessed is not None:
            # 计算当前时间和上次访问时间的间隔,然后被规则间隔减去,
            # 如果大于0,说明间隔时间不到,否则直接下载下个网页
            # sleep_interval加上随机偏移量,会更加真实
            sleep_interval = self.delay - (datetime.now() - last_accessed).seconds
            if sleep_interval > 0:
                time.sleep(sleep_interval)
        self.domains[domain_url] = datetime.now()

最后传入网址就可以爬取了,里面的一些匹配规则根据你所需要的来定,不要盲目抄袭,没有意义。

猜你喜欢

转载自blog.csdn.net/oyjl19961216/article/details/82806759