完整爬虫

主要代码:

import random

import requests
from fake_useragent import UserAgent
from retrying import  retry   #重置下载
import hashlib    #信息摘要算法 md5
import queue    #队列
import re   #正则
from urllib import robotparser  #解析网站的robots.txt文件
from urllib.parse import  urlparse,urljoin,urldefrag    #解析url
from threading import Thread   #多线程
from datetime import datetime
import time
import mongo_cache


MAX_DEP = 2     #定义爬虫爬取深度

def get_robots(url):
    """
    解析robots.txt 文件
    :param url:
    :return:
    """
    rp = robotparser.RobotFileParser()
    rp.set_url(urljoin(url,'robots.txt'))
    rp.read()
    return rp

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


def gen_html_name(url_str):

    #获取域名后面的部分 urlparse('http://www.baidu.com/a/s/d').path    '/a/s/d'
    path = urlparse(url_str).path

    #按照/切割 array:数组
    path_array = path.split('/')

    #取出最后一个
    return path_array[len(path_array)-1]

    #extractor:抽取,提取

def extractor_url_lists(html_content):
    """
    抽取网页中的其他链接
    :param html_content:
    :return:
    """
    #复习
    # ^:[]外面是以什么开头的意思,在[]里面时是 非 的意思
    # [^>] a  href 之间不能有 > ,不是>的都可以匹配
    #["\']  " ' 都能匹配,\ 转义
    # (.*?) 匹配任意个任意字符  ? :取消贪婪匹配  ():把匹配的内容获取出来 分组
    #regex:正则
    url_regex = re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
    return url_regex.findall(html_content)

class CrawlerCommon(Thread):
    """
    实现一个通用爬虫,涵盖基本的爬虫功能
    """
    def __init__(self,init_url):

        #py2.7的写法
        # super(CrawlerCommon,self).__init__()

        #py3.x 的写法  强制调用父类函数
        super().__init__()

        __ua = UserAgent()     #随机User-Agent
        self.seed_url = init_url    #初始爬取的种子网址
        self.crawler_queue = queue.Queue()  #使用不同的队列会造成BFS和DFS的效果
        self.crawler_queue.put(init_url)    #将种子网址放入队列
        self.visited = {init_url : 0}   #初始化爬取深度为0  visited :访问
        self.rp = get_robots(init_url)  #初始化robots解析器
        self.headers = {"User-Agent":__ua.random}   #生成一个随机的User-Agent
        self.link_regex = '(index|view)' #抽取网址的过滤条件  link:链接

        #跳到限流器__init__
        self.throttle = Throttle(3.0)   #下载限流器间隔5秒
        self.mcache = mongo_cache.MongoCache()  #初始化 mongo_cache
        # self.time_sleep = 2

    #attempt:尝试
    @retry(stop_max_attempt_number=3)
    def retry_download(self,url_str,data,method,proxies):
        """
        retry:重试

        使用装饰器的重试下载类
        :param url_str:
        :param data:
        :param method:
        :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

#dir() 看到类内部的实现方法

    def download(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.message)
            result = None
        return result


    def nomalize(self,url_str):
        """
        补全下载链接
        :param url_str:
        :return:
        """
        #打散网址   urldefrag:截取URL中#前面的网址
        real_url,_ = urldefrag(url_str)
        #拼接路径
        return urljoin(self.seed_url,real_url)

    def save_result(self,html_content,url_str):
        """
        将结果存入数据库,存入前检查内容是否存在
        :param html_content: 下载的二进制内容
        :param url_str: 下载网页的url
        :return:
        """
        if url_str not in self.mcache:
            self.mcache[url_str] = html_content
        else:
            data_from_mongo = self.mcache[url_str]

            #初始化md5算法
            md5_func_mongo = hashlib.md5()
            md5_func_download = hashlib.md5()

            #生成数据库记录的md5摘要
            md5_func_mongo.update[data_from_mongo]
            mongo_md5_str = md5_func_mongo.hexdigest()

            md5_func_download.update(html_content)
            download_md5_str = md5_func_download.hexdigest()

            if download_md5_str != mongo_md5_str:
                self.mcache[url_str] = html_content
    #高内聚 低耦合
    def run(self):
        """
        进行网页爬取的主要方法
        :return:
        """
        #判断队列是否为空
        while not self.crawler_queue.empty():

            #将网址从队列中取出来
            url_str = self.crawler_queue.get()

            #检测robots.txt文件规则
            # 改为if True: 直接下载
            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.download(url_str)
                    #存储链接
                    if html_content is not None:
                        #存库
                        self.mcache[url_str] = html_content
                        save_url(html_content,url_str)
                    else:
                        continue
                    print(self.mcache[url_str])

                    #筛选出页面所有链接
                    url_list = extractor_url_lists(html_content.decode('utf8'))

                    #筛选出需要爬去的连接                                   self.link_refex
                    filter_urls = [link for link in url_list if re.search('/(html)',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)

#随机代理
class RandomProxy(object):
    """
    随机代理
    """

    def __init__(self):
        self.proxies = {}
        self.headers = {
            "User-Agent":"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0"
        }

    def crawl_proxies(self):
        """
        抓取生成代理
        :return:
        """
        self.proxies.append('114.223.163.70')
        self.proxies.append('125.40.79.66')

    def verify_proxies(self):
        """
        校验每一个代理是否可用
        :return:
        """
        invalid_ip = {}
        for ip_str in self.proxies:
            proxies = {"http": ip_str}
            r = requests.get("", proxies=proxies, headers=self.headers)
            if r.status_code == 200:
                continue
            else:
                invalid_ip.append(ip_str)

        for remove_ip in invalid_ip:
            self.proxies.remove(remove_ip)

    def get_one_proxy(self):
        return random.choice(self.proxies)


class Throttle(object):
    """
    下载限流器  throttle:限流    time.sleep()
    """
    def __init__(self,delay):
        #domains:域名
        self.domains = {}

        #两次下载间隔时间
        self.delay = delay


    def wait_url(self,url_str):


        #获取到爬取的网址的域名          net location :网络位置
        domain_url = urlparse(url_str).netloc

        #获取上次爬取的时间戳
        last_accessed = self.domains.get(domain_url)

        #爬去的条件为上次爬去的时间不为空(如果没有爬取则把这个域名和当前时间戳保存到字典中)
        if self.delay > 0 and last_accessed is not None:
            #计算当前时间和上次访问时间间隔
            #sleep_interval加上随机偏移量

            #记录上次爬取到这次的时间间隔  interval:间隔                      seconds:转化为秒
            sleep_interval = self.delay - (datetime.now() - last_accessed).seconds

            #如果时间间隔大于0休眠,否则直接下载
            if sleep_interval > 0:
                #设置一个随机的偏移量
                time.sleep(sleep_interval)

        #记录爬取这个链接的时间戳  域名为key,当前时间为value  存到domains字典中
        self.domains[domain_url] = datetime.now()


if __name__ == '__main__':

    #1,调用器构造方法
    crawler = CrawlerCommon("https://www.runoob.com/html/html5-intro.html")

    #调用run()方法
    crawler.start()

连接数据库代码:



import pickle
import zlib
from datetime import datetime,timedelta
from pymongo import MongoClient
from bson.binary import Binary


class MongoCache(object):
    """
    数据库缓存
    """
    #timedelta 时间间隔
    def __init__(self,client=None,expires=timedelta(days=30)):
        self.client = MongoClient("localhost",27017)
        self.db = self.client.cache

        #加速查找,设置索引,设置超时时间  timestamp:时间戳
        # 如果达到 expireAfterSeconds 设置的时间,mongodb会把超时数据自动删除
        self.db.webpage.create_index('timestamp',expireAfterSeconds=expires.total_seconds())

    #__setitem__ : 每当属性被赋值时调用该方法,不能在该方法内赋值,会造成死循环
    def __setitem__(self, key, value):

        #三步压缩
        record = {"result":Binary(zlib.compress(pickle.dumps(value))),"timestamp":datetime.utcnow()}

        #upsert:不存在插入,存在更新      $set:强制覆盖原始数据,mongodb的内置函数(带$)
        self.db.webpage.update({"_id":key},{'$set':record},upsert=True)

    #__getitem__:当访问不存在的属性时会调用该方法
    def __getitem__(self, item):
        #根据id 以item作为关键字(例如:url:http://www.baidu.com)查找相关网页
        record = self.db.webpage.find_one({"_id":item})
        if record:
            #解压缩,反序列化
            return pickle.loads(zlib.decompress(record["result"]))
        else:
            #模拟找不到关键字,抛出异常
            raise KeyError(item + "does not exist")
    #当使用 in ,not in 对象时调用
    def __contains__(self, item):
        try:
            #执行 __getitem__ 方法
            self[item]
        except KeyError:
            #捕获到keyError 异常,说明没找到相关数据,参考3行抛出异常的条件
            return False
        else:
            #找到相应数据说明数据库包含下载内容
            return True

    def clear(self):
        #清空缓存库
        self.db.webpage.drop()

mo = MongoCache()
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值