实验1:域名信息收集工具

本项目已同步到GitHub:https://github.com/Austinggg/Cyber_attack-_and_defense_technology_SCU_2024/tree/main/lab-01
如果觉得本篇文章对你有帮助,可以点亮⭐支持,同时给本篇博文点赞转发收藏,让我们共同走的更远。

实验内容:

本次实验主要考察大家的编程能力及子域名的信息收集方法,在文件夹“Lab1_code”提供了使用Bing搜索引擎的域名收集功能。请对该代码进行扩展,使其可支持百度搜索引擎的域名收集功能。需要实现如下功能:

  1. 支持百度搜索引擎的域名提取,其中从百度搜索引擎提取的域名需为真实域名,而非百度的域名跳转链接;
  2. 可扩充其他功能,比如域名所在的标题等信息。

实验步骤:

确定基本请求 URL

  1. 使用 site: 命令在百度搜索引擎中搜索子域名,例如:site:qq.com
    在这里插入图片描述

  2. 在网址栏复制百度查找目标域名下的子域名的搜索语法

https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=site%3A%20qq.com&fenlei=256&rsv_pq=0x9dd71ab301a26022&rsv_t=6cb7DHPHPzTnauxun84nZ46229FixqYO3Oc3Tu669p09QcemcKu8zqjOdkhC&rqlang=en&rsv_dl=tb&rsv_enter=1&rsv_sug3=15&rsv_sug1=9&rsv_sug7=101&rsv_sug2=0&rsv_btype=t&prefixsug=site%253A%2520qq.com&rsp=6&inputT=3900&rsv_sug4=3900

  1. 使用获取的url,修改示例代码中的请求 url,使用变量domain表示我们的查找的目标域名,使其能够使用百度的搜索语法查找目标域名下的子域名。
url = f"https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd=site%3A{domain_request}"

分页控制与 URL 生成

  1. 观察到百度搜索引擎的搜索结果会分页显示在百度搜索引擎中,,,我们需要在请求 URL 中添加分页参数。分页参数通常以pn表示,其值从 0 开始,每页显示 10 条结果。因此,我们可以通过循环生成不同的分页 URL 来获取所有搜索结果。
# 循环生成不同的分页 URL
url = f"https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd=site%3A{domain_request}&pn={page_num * 10}"

page_num = 0  # page_number 从 0 开始递增,每次增加 1

# 循环获取所有搜索结果
'''
while page_num < max_page_num:
    # 请求百度搜索引擎
    response = requests.get(url, headers=headers)
    # 解析搜索结果
    soup = BeautifulSoup(response.text, 'html.parser')
    # 获取搜索结果列表
    results = soup.find_all('div', class_='c-container')

    page_num += 1

'''

请求头设计

  1. 修改请求头信息,使其能够伪装成浏览器访问百度搜索引擎。
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"  
    # User-Agent 模拟了一个较为常见的 Chrome 浏览器版本。
    'Referer': 'https://www.baidu.com',  # 引用来源,模拟从其他页面跳转过来
}

处理跳转链接

百度的搜索结果链接可能是跳转链接,需要额外处理来获取真实的 URL:

  1. 利用检查功能,查看搜索结果中的真实域名.
    请添加图片描述

  2. 我们可以看到,百度搜索引擎的搜索结果中,真实域名被百度搜索引擎的域名跳转链接所替代,真实的域名藏在 class'result'<div> 标签 后的 'mu'属性后。
    请添加图片描述

  3. 在提供的示例代码中添加过滤功能后,即可提取出真实域名。

# 查找所有 class 为 'result' 的 <div> 标签,百度的搜索结果一般包含在这些标签中
job_bt = soup.find_all('div', attrs={'class': 'result'})
# 如果没有找到任何结果,结束循环
if not job_bt:
    print("No more results found.")

# 遍历查找到的每个 <div> 标签,提取子域名和描述
for i in job_bt:
    link = i.get('mu')  # 获取 class 为 'result' 的 <div> 标签的 'mu' 属性值,即子域名的链接
    if link:  # 如果链接存在,进一步解析子域名
    # 使用 urlparse 解析 URL,获取 URL 的协议 (scheme) 和网络位置 (netloc)
    domain = str(urlparse(link).scheme + "://" + urlparse(link).netloc)

    Subdomain.append(domain)  # 将新发现的域名加入子域名列表
    print(f"{domain}")  # 打印新发现的域名

随机延迟与异常处理

  1. 为避免被目标网站封禁,我们需要在每次请求之间添加随机延迟。
page_num += 1  # 页码加 1,准备抓取下一页
time.sleep(random.uniform(2, 5))  # 添加随机延时 2 到 5 秒,模拟人工操作
  1. 为了处理可能出现的异常,如网络请求失败或解析错误,我们需要添加异常处理代码。
while page_num < max_pages:
    # 构造请求的 URL,使用百度的搜索语法查找目标域名下的子域名
    url = f"https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd=site%3A{domain_request}&pn={page_num * 10}"
    try:
        # 发送 HTTP GET 请求获取网页源码,设置超时时间为 10 秒
        resp = requests.get(url, headers=headers, timeout=10)
            
        # 检查请求状态码是否成功
        if resp.status_code != 200:
            print(f"Failed to retrieve page {page_num}, status code: {resp.status_code}")
            # 如果遇到请求过多的情况 (状态码 429),随机休眠一段时间再继续
            if resp.status_code == 429:
                print("Too many requests, sleeping for a while...")
                time.sleep(random.randint(60, 120))  # 随机睡眠 1 到 2 分钟
                break  # 跳出循环,避免进一步请求
        
        '''获取子域名...'''

    except requests.RequestException as e:
        print(f"Request failed: {e}")
        # 判断是否是429(Too Many Requests)或其他错误状态码,增加等待时间
        if "429" in str(e) or "403" in str(e):
            print("Detected rate limiting or forbidden access, sleeping for longer...")
            time.sleep(random.randint(180, 300))  # 休眠 3 到 5 分钟再尝试
        else:
            time.sleep(random.randint(30, 60))  # 常规休眠 30 到 60 秒后重试
            continue

去重与存储

  1. 在获取子域名后,我们需要对子域名进行去重处理,我们使用一个列表来存储已发现的子域名,并在每次发现新的子域名时检查它是否已经存在于列表中。如果不存在,则将其添加到列表中并打印出来。
# 如果解析后的域名已经存在于 Subdomain 列表中则跳过,否则将域名添加到子域名列表中
    if domain not in Subdomain:
        Subdomain.append(domain)  # 将新发现的域名加入子域名列表
        print(f"{domain}")  # 打印新发现的域名
        found_domains += 1  # 新发现一个域名,计数加 1

获取子域名的描述

我们在看到域名时,可能无法立马知道这个域名的用途,所以我们可以获取该域名的描述信息。步骤类似于获取子域名。

  1. 在刚才打开的检查窗口查看该域名的描述信息。
    在这里插入图片描述
  2. 在代码中添加获取描述信息的部分,可以看到 title 信息放在 <h3> 标签中。
# 获取子域名的描述,提取 <h3> 标签内的文本
string = i.find('h3').get_text() if i.find('h3') else "No Title"  

完善反爬机制

  1. 使用 fake_useragent 库生成随机的 User-Agent,模拟浏览器访问。
from fake_useragent import UserAgent     # 用于生成随机的User-Agent,模拟浏览器访问
ua = UserAgent()  # 初始化 UserAgent 对象,用于随机生成 User-Agent 字符串

# 定义请求头,绕过反爬机制,模拟浏览器访问
    headers = {
        'User-Agent': ua.random,  # 使用随机的 User-Agent 模拟常见的浏览器请求
        'Accept': '*/*',  # 接受所有类型的响应
        'Referer': 'https://www.baidu.com',  # 引用来源,模拟从其他页面跳转过来
    }

完整代码

import random
import time
import requests                          # 用于发送HTTP请求获取网页内容
from bs4 import BeautifulSoup            # 用于解析和处理获取的网页源码数据
from urllib.parse import urlparse        # 用于解析URL,提取协议、网络位置等信息
from fake_useragent import UserAgent     # 用于生成随机的User-Agent,模拟浏览器访问

# 定义一个函数,用于通过百度搜索子域名
def baidu_search(domain_request, max_pages=100):
    Subdomain = []  # 定义一个空列表,用于存储收集到的子域名
    ua = UserAgent()  # 初始化 UserAgent 对象,用于随机生成 User-Agent 字符串

    page_num = 0  # 已搜索的页数初始化为 0
    found_domains = 0  # 已找到的域名数量初始化为 0

    # 定义请求头,绕过反爬机制,模拟浏览器访问
    headers = {
        'User-Agent': ua.random,  # 使用随机的 User-Agent 模拟常见的浏览器请求
        'Accept': '*/*',  # 接受所有类型的响应
        'Referer': 'https://www.baidu.com',  # 引用来源,模拟从其他页面跳转过来
    }
    
    # 循环请求每一页的搜索结果,最多请求 max_pages 页
    while page_num < max_pages:
        # 构造请求的 URL,使用百度的搜索语法查找目标域名下的子域名
        url = f"https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd=site%3A{domain_request}&pn={page_num * 10}"

        try:
            # 发送 HTTP GET 请求获取网页源码,设置超时时间为 10 秒
            resp = requests.get(url, headers=headers, timeout=10)
            
            # 检查请求状态码是否成功
            if resp.status_code != 200:
                print(f"Failed to retrieve page {page_num}, status code: {resp.status_code}")
                # 如果遇到请求过多的情况 (状态码 429),随机休眠一段时间再继续
                if resp.status_code == 429:
                    print("Too many requests, sleeping for a while...")
                    time.sleep(random.randint(60, 120))  # 随机睡眠 1 到 2 分钟
                break  # 跳出循环,避免进一步请求

            # 使用 BeautifulSoup 解析获取到的网页源码,指定使用 'html.parser' 作为解析器
            soup = BeautifulSoup(resp.content, 'html.parser')

            # 查找所有 class 为 'result' 的 <div> 标签,百度的搜索结果一般包含在这些标签中
            job_bt = soup.find_all('div', attrs={'class': 'result'})

            # 如果没有找到任何结果,结束循环
            if not job_bt:
                print("No more results found.")
                break

            # 遍历查找到的每个 <div> 标签,提取子域名和描述
            for i in job_bt:
                link = i.get('mu')  # 获取 class 为 'result' 的 <div> 标签的 'mu' 属性值,即子域名的链接
                if link:  # 如果链接存在,进一步解析子域名
                    # 使用 urlparse 解析 URL,获取 URL 的协议 (scheme) 和网络位置 (netloc)
                    domain = str(urlparse(link).scheme + "://" + urlparse(link).netloc)
                    # 获取子域名的描述,提取 <h3> 标签内的文本
                    string = i.find('h3').get_text() if i.find('h3') else "No Title"  

                    # 如果解析后的域名已经存在于 Subdomain 列表中则跳过,否则将域名添加到子域名列表中
                    if domain not in Subdomain:
                        Subdomain.append(domain)  # 将新发现的域名加入子域名列表
                        print(f"{domain}\t{string}")  # 打印新发现的域名及其描述
                        found_domains += 1  # 新发现一个域名,计数加 1

            page_num += 1  # 页码加 1,准备抓取下一页
            time.sleep(random.uniform(2, 5))  # 添加随机延时 2 到 5 秒,模拟人工操作

        except requests.RequestException as e:
            print(f"Request failed: {e}")
            # 判断是否是429(Too Many Requests)或其他错误状态码,增加等待时间
            if "429" in str(e) or "403" in str(e):
                print("Detected rate limiting or forbidden access, sleeping for longer...")
                time.sleep(random.randint(180, 300))  # 休眠 3 到 5 分钟再尝试
            else:
                time.sleep(random.randint(30, 60))  # 常规休眠 30 到 60 秒后重试
            continue

    # 汇总打印搜索结果
    print("-----------------------------\n",
          f"对于域名 {domain_request} \n",
          f"共搜索了 {page_num} 页 \n",
          f"共找到 {found_domains} 个子域名。")

# 调用定义的函数,开始收集子域名
# 参数1:目标域名,参数2:要抓取的页数
baidu_search('qq.com', 100)

运行结果示例

此处的运行结果过长,截取部分作为展示

在这里插入图片描述

实验过程问题总结

  1. 与 bing 不同,百度采用的是跳转链接,因此我们需要重新观察页面代码,找到真正的域名的链接,不能使用相同的方法进行爬取。若采用相同的方法,则只会出现下面的6个域名:

在这里插入图片描述

  1. 在程序运行后,立即重新运行,会出现找不到子域名的情况,是因为频繁访问,被百度限制访问,即使我增设了延时功能,但仍不能解决该问题。考虑采用代理 ip 池来解决问题。

请添加图片描述

参考资料

[1] https://blog.csdn.net/qq_46145027/article/details/124133278
[2] https://blog.csdn.net/qq_53184526/article/details/126853601
[3] https://developers.google.cn/search/docs/monitor-debug/search-operators/all-search-site?hl=zh-cn
[4] https://blog.csdn.net/w1590191166/article/details/104160404

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值