python 进阶版(爬虫示例)

具体实现的功能:
每5分钟爬取阿里云漏洞库的页面,找到漏洞评分大于9分的漏洞并告警,再通过阿里云的API找出存在这些新增漏洞的服务器。
整体思路:(总共有5个模块)
1、漏洞类AVD:属性有漏洞详情,漏洞名,漏洞id,漏洞评分(属性用变量表示,这种变量叫实例变量);
漏洞类里还有通过API获取漏洞类型的函数def avd_gettype(self, client)以及存在漏洞的服务器信息的函数def avd_getuuid(self, client),需要传入API的客户端的参数;
2、数据库连接函数connect_db(),并返回一个连接,便于连接函数
3、游标函数conndb_cursor(conn),便于爬取页面时对数据库进行插入操作
4、爬虫函数grab(client, cursor, url),需要传入的参数有客户端,游标,爬取的页面地址。
5、钉钉群消息的推送dingding(mes),要注意钉钉群机器人关键字的设置,没有关键字就会被屏蔽掉

import json
import sqlite3
# 时间模块time
import time
from operator import itemgetter

# 下载网页使用requests模块
import requests
# 解析HTML,得到目标数据使用BeautifulSoup模块
from bs4 import BeautifulSoup

from alibabacloud_sas20181203.client import Client as Sas20181203Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_sas20181203 import models as sas_20181203_models

# API的AK值
access_key_id = 'key_id'
access_key_secret = 'key_secret'

# # 把数据库表里的漏洞id放入列表,便于后续表数据的查询,程序直接查询列表(即内存地址)比查询数据库更高效
avd_id_list = []


class AVD:
    """
    这是一个关于漏洞的类,有漏洞的AVD编号avd_id,漏洞名称avd_name,漏洞危险评分avd_sorce,漏洞的详情页面avd_href
    """
    # 创建类变量
    # 漏洞详情页面
    avd_url = ''
    # 漏洞名
    avd_name = ''
    # 漏洞编号
    avd_id = ''
    # 漏洞危险评分
    avd_sorce = 0.0

    # 构造函数,传入漏洞的详情页面的参数,便可获得漏洞的名字,编号,危险评分
    def __init__(self, detail_url):
        d_response = requests.get(url=detail_url)
        detail_object = BeautifulSoup(d_response.text, "html.parser", from_encoding="utf-8")
        # .text获取标签的文本内容
        d_name = detail_object.find('span', class_='header__title__text').text
        # print('d_name', d_name)
        d_score = float(detail_object.find('div', class_='cvss-breakdown__score cvss-breakdown__score--high').text.strip())
        # print('d_score', d_score)
        # find_all('li', class_="breadcrumbs__list-item-last CMSBreadCrumbsLink")[2]找到li标签下的第3个元素
        d_id = detail_object.find('ol', class_="breadcrumbs__list").find_all('li',class_="breadcrumbs__list-item-last CMSBreadCrumbsLink")[2].text.strip()
        # print('d_id', d_id)
        # 初始化类变量
        self.avd_url = detail_url
        self.avd_name = d_name
        self.avd_id = d_id
        self.avd_sorce = d_score

    # 通过漏洞的相关信息获取有新漏洞服务器的uuid
    # 通过DescribeGroupedVul找到漏洞的类型type
    def avd_gettype(self, client):
        describe_grouped_vul_request = sas_20181203_models.DescribeGroupedVulRequest(
            alias_name=self.avd_name
        )
        groupedvulitems = client.describe_grouped_vul(describe_grouped_vul_request)
        avd_type = groupedvulitems.body.grouped_vul_items
        if len(avd_type) == 0:
            print(self.avd_name, '没有找到该漏洞类型')
            return ''
        return avd_type[0].type

    # 再通过DescribeVulList API得到有此漏洞的服务器的uuid
    def avd_getuuid(self, client):
        tp = self.avd_gettype(client)
        if tp == '':
            print('没有服务器有该漏洞')
            return ''
        else:
            describe_vul_list_request = sas_20181203_models.DescribeVulListRequest(
                type=tp,
                alias_name=self.avd_name
            )
            vul_records = client.describe_vul_list(describe_vul_list_request)
            uuid = vul_records.body.vul_records[0].uuid
            print('存在该漏洞的服务器为:', uuid)
            dd_uuid = {
                "at": {
                    "atMobiles": [
                        "180xxxxxx"
                    ],
                    "atUserIds": [
                        "user123"
                    ],
                    "isAtAll": False
                },
                "text": {
                    "content": "存在新增高危漏洞:\n%s\n出现该漏洞的服务器为:\n%s" % (self.avd_name, uuid)
                },
                "msgtype": "text"
            }
            dingding(dd_uuid)
            return uuid


# 得到云安全中心的客户端
def client_sas():
    global access_key_id
    global access_key_secret
    config = open_api_models.Config(access_key_id, access_key_secret)
    config.endpoint = f'tds.aliyuncs.com'
    client_s = Sas20181203Client(config)
    # 返回一个客户端,传到要调用aip的函数
    return client_s


# 建立数据库连接
def connect_db():
    # 建立数据库连接,如果不存在将会创建该数据库
    conn = sqlite3.connect('D:/vul.db')
    # 事务隔离级别,默认是需要自己commit才能修改数据库,置为None自动每次修改都提交
    conn.isolation_level = None
    # 返回一个连接,可传递连接参数使用数据库
    return conn


# 建表,以及查询表
# 把连接参数conn传入建表,查表的函数conndb_cursor(),进行对数据库vul.db的操作
def conndb_cursor(conn):
    # 创建游标
    cursor = conn.cursor()
    # 创建表
    sql_create = 'create table if not exists avd_table(avd_id varchar(30) PRIMARY KEY, avd_name varchar(30), avd_score integer)'
    try:
        cursor.execute(sql_create)
    except Exception as e:
        print('抛出异常', e)
        exit(1)
    # 因为漏洞id是唯一的,所以后面新增数据只需依据表内是否已经有漏洞id即可
    sql_selectID = 'select avd_id from avd_table'
    cursor.execute(sql_selectID)
    # fetchall()返回的是有多个元组的 列表,
    fetchall = cursor.fetchall()
    # print(fetchall)
    if len(fetchall):
        # 循环把fetchall列表里的元组的 第一个元素放入列表中
        for i in fetchall:
            global avd_id_list
            avd_id_list.append(i[0])
            # print(i)
        # print(len(avd_id_list))
        # print(avd_id_list)
    else:
        print('表内还没有数据')
    # 最后返回一个游标,把游标参数传入到爬取页面的函数,进行插入数据的操作
    return cursor


# 钉钉机器人消息推送,此函数是为了让机器人推送新增高危漏洞的消息
def dingding(mes):
    # 机器人的webhook地址
    webhook = 'webhook地址'
    # 请求头
    headers = {'content-type': 'application/json'}
    r = requests.post(webhook, headers=headers, data=json.dumps(mes))
    r.encoding = 'utf-8'
    return r.text

# 分析主页面,获取副页面的页面数page
def parse_url(url):
    response = requests.get(url)
    htm_page = BeautifulSoup(response.text, "html.parser", from_encoding="utf-8")
    spqn_page = htm_page.find('span', class_="text-muted")
    print(spqn_page.text)

    print(spqn_page.text[8:10])


# 爬取页面获取数据,并把数据插入表中
def grab(client, cursor, url):
    # 请求并下载网页,r禁止字符转义
    # response = requests.get(r'https://avd.aliyun.com/high-risk/list?page=1')
    response = requests.get(url)
    # 将网页源码构造成一个beautifulSoup解析对象,html.parser网页解析器
    high_risk_list = BeautifulSoup(response.text, "html.parser", from_encoding="utf-8")
    # 获取a标签的对象a_node,循环获取到所有a标签的href
    a_label = high_risk_list.find('tbody').find_all('a')

    # 循环获取a标签的子链接
    # 判断a标签的页面有没有数据,没有数据就返回0
    if len(a_label) == 0:
        print('该页面没有漏洞数据')
        return 0
    # print(len(a_label), 'a标签')
    # return 1
    for a in a_label:
        detail = 'https://avd.aliyun.com' + a.get('href')
        # 把子链接传入AVD(),漏洞实例化(AVD()类的具体对象):vul_item
        vul_item = AVD(detail)
        # print(vul_item.avd_name)
        # 把新增的漏洞信息插入表中
        if vul_item.avd_id not in avd_id_list:
            # print(vul_item.avd_name, vul_item.avd_id, '不存在表中')
            sql_insert = 'insert into avd_table(avd_id,avd_name,avd_score) values("%s","%s","%s")' % (vul_item.avd_id, vul_item.avd_name, vul_item.avd_sorce)
            cursor.execute(sql_insert)
            # 还要更新avd_id_list列表
            avd_id_list.append(vul_item.avd_id)
            # print(type(vul_item.avd_sorce), vul_item.avd_sorce)
            # print('发现新增高危漏洞:', vul_item.avd_name)
            if vul_item.avd_sorce > 9.0:
                # -------找出存在新增漏洞的服务器------
                vul_item.avd_getuuid(client)
                # -------向钉钉发送推送消息-----------
                dding = {
                    "msgtype": "link",
                    "link": {
                        "text": vul_item.avd_name,
                        "title": "新增高危漏洞",
                        "picUrl": "",
                        "messageUrl": url
                    }
                }
                dingding(dding)
    return 1


if __name__ == '__main__':
    # 清空列表元素
    # avd_id_list.clear()

    # 创建客户端
    client = client_sas()
    # 创建数据库连接
    conn = connect_db()
    # 创建表,获得一个数据库的游标
    cursor = conndb_cursor(conn)

    # 清空表中数据
    # cursor.execute('delete from avd_table')

    # 爬取所有页面
    for i in range(1, 3):
        # parse_url(r'https://avd.aliyun.com/high-risk/list')
        j = 0
        # 一直循环访问所有漏洞的副页面
        while True:
            j = j+1
            # 爬取所有漏洞页面
            pre_url = r'https://avd.aliyun.com/high-risk/list?page=' + str(j)
            print(pre_url)
            res = grab(client, conn, pre_url)
            # 判断grab()返回的结果,返回0的结果就退出循环
            if res == 0:
                print('退出本次%d爬取', i)
                break
        time.sleep(5*60)

    sql_select = 'select * from avd_table'
    cursor.execute(sql_select)
    print('----查询表的数据-----')
    data = cursor.fetchall()
    for d in data:
        print(d)

    cursor.close()
    conn.close()

你可能感兴趣的:(python,爬虫,python,安全)