golang实现从request请求返回的response中提取网站图标的faviconMMH3, faviconMD5, faviconPath, faviconData, faviconURL

golang实现从request请求返回的response中提取网站图标的faviconMMH3, faviconMD5, faviconPath, faviconData, faviconURL,其中faviconData类型为[]byte,其余为string类型。

在Go中提取网站的favicon(网站图标)并计算其MMH3和MD5哈希值,同时获取路径、原始数据和URL,可以通过以下步骤实现:

package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/twmb/murmur3"
	"golang.org/x/net/html"
)

// FaviconInfo 存储提取的favicon信息
type FaviconInfo struct {
	MMH3      string // MurmurHash3哈希值
	MD5       string // MD5哈希值
	Path      string // 图标路径
	Data      []byte // 原始图标数据
	URL       string // 完整图标URL
}

// ExtractFavicon 从HTTP响应中提取favicon信息
func ExtractFavicon(resp *http.Response) (*FaviconInfo, error) {
	baseURL := resp.Request.URL
	contentType := resp.Header.Get("Content-Type")

	// 1. 尝试从HTML中提取favicon URL
	var faviconURLs []string
	if strings.Contains(contentType, "text/html") {
		// 读取并重置响应体以便后续使用
		bodyBytes, err := io.ReadAll(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("failed to read response body: %w", err)
		}
		resp.Body.Close()
		resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))

		// 解析HTML获取favicon链接
		faviconURLs = extractFaviconURLs(bodyBytes, baseURL)
	}

	// 2. 添加默认favicon位置
	if len(faviconURLs) == 0 {
		defaultFavicon, _ := url.Parse("/favicon.ico")
		faviconURLs = append(faviconURLs, baseURL.ResolveReference(defaultFavicon).String())
	}

	// 3. 尝试下载favicon
	client := &http.Client{
		Timeout: 5 * time.Second,
	}
	
	for _, favURL := range faviconURLs {
		info, err := downloadAndProcessFavicon(client, favURL, resp.Request.Header.Get("User-Agent"))
		if err == nil {
			return info, nil
		}
	}
	
	return nil, fmt.Errorf("no valid favicon found after %d attempts", len(faviconURLs))
}

// extractFaviconURLs 从HTML内容中提取所有可能的favicon URL
func extractFaviconURLs(body []byte, baseURL *url.URL) []string {
	var urls []string
	seen := make(map[string]bool)

	doc, err := html.Parse(bytes.NewReader(body))
	if err != nil {
		return nil
	}

	var f func(*html.Node)
	f = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "link" {
			var href, rel string
			for _, attr := range n.Attr {
				switch strings.ToLower(attr.Key) {
				case "href":
					href = attr.Val
				case "rel":
					rel = attr.Val
				}
			}

			rel = strings.ToLower(rel)
			if strings.Contains(rel, "icon") || strings.Contains(rel, "shortcut") {
				if href != "" {
					absURL, err := baseURL.Parse(href)
					if err == nil {
						urlStr := absURL.String()
						if !seen[urlStr] {
							seen[urlStr] = true
							urls = append(urls, urlStr)
						}
					}
				}
			}
		}
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			f(c)
		}
	}
	f(doc)

	return urls
}

// downloadAndProcessFavicon 下载并处理favicon
func downloadAndProcessFavicon(client *http.Client, favURL, userAgent string) (*FaviconInfo, error) {
	req, err := http.NewRequest("GET", favURL, nil)
	if err != nil {
		return nil, err
	}

	// 设置合理的请求头
	req.Header.Set("User-Agent", userAgent)
	req.Header.Set("Accept", "image/*,*/*;q=0.8")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
	}

	// 限制读取大小 (最大1MB)
	data, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
	if err != nil {
		return nil, err
	}
	if len(data) == 0 {
		return nil, fmt.Errorf("empty favicon data")
	}

	// 计算哈希值
	md5Hash := md5.Sum(data)
	mmh3Hash := murmur3.Sum32(data)

	// 获取路径信息
	u, _ := url.Parse(favURL)
	path := u.Path
	if path == "" {
		path = "/"
	}

	return &FaviconInfo{
		MMH3:      fmt.Sprintf("%d", int32(mmh3Hash)),
		MD5:       hex.EncodeToString(md5Hash[:]),
		Path:      path,
		Data:      data,
		URL:       resp.Request.URL.String(), // 获取最终URL(处理重定向后)
	}, nil
}

func main() {
	// 示例URL
	targetURL := "https://example.com"

	// 发送HTTP GET请求
	resp, err := http.Get(targetURL)
	if err != nil {
		fmt.Printf("请求失败: %v\n", err)
		return
	}
	defer resp.Body.Close()

	// 提取favicon信息
	faviconInfo, err := ExtractFavicon(resp)
	if err != nil {
		fmt.Printf("提取favicon失败: %v\n", err)
		return
	}

	// 打印结果
	fmt.Printf("Favicon URL: %s\n", faviconInfo.URL)
	fmt.Printf("Favicon Path: %s\n", faviconInfo.Path)
	fmt.Printf("Favicon MMH3: %s\n", faviconInfo.MMH3)
	fmt.Printf("Favicon MD5: %s\n", faviconInfo.MD5)
	fmt.Printf("Favicon Size: %d bytes\n", len(faviconInfo.Data))
}

关键功能说明:

  1. Favicon位置发现

    • 解析HTML查找标签
    • 自动将相对路径转换为绝对URL
    • 如果未找到,尝试默认的/favicon.ico
  2. 数据下载与处理

    • 使用自定义HTTP客户端(设置5秒超时)
    • 限制读取大小(最大1MB)
    • 处理重定向和错误状态码
    • 保留最终URL(处理重定向后)
  3. 哈希计算

    • MurmurHash3 (MMH3):使用github.com/twmb/murmur3计算32位哈希值
    • MD5:使用标准库计算MD5哈希
    • 结果转换为字符串格式
  4. 数据结构

    type FaviconInfo struct {
        MMH3      string // MurmurHash3哈希值
        MD5       string // MD5哈希值
        Path      string // 图标路径
        Data      []byte // 原始图标数据
        URL       string // 完整图标URL
    }
    

安装依赖:

go get github.com/twmb/murmur3
go get golang.org/x/net/html

处理流程:

  1. 获取网页响应
  2. 解析HTML查找favicon链接
  3. 尝试所有候选URL直到成功
  4. 下载favicon数据
  5. 计算MMH3和MD5哈希
  6. 提取路径和最终URL
  7. 返回结构化的favicon信息

特殊处理:

  • 内容类型检查:只对HTML内容进行解析
  • 响应体重置:读取后重置响应体以便后续使用
  • URL去重:避免重复尝试相同URL
  • 用户代理:继承原始请求的User-Agent
  • 大小限制:防止读取过大文件
  • 路径处理:确保空路径显示为"/"

输出示例:

Favicon URL: https://example.com/favicon.ico
Favicon Path: /favicon.ico
Favicon MMH3: 1234567890
Favicon MD5: d7a8fbb307d7809469ca9abcb0082e4f
Favicon Size: 5432 bytes

注意事项:

  1. 需要处理可能的多重favicon声明
  2. 考虑网站可能使用CDN或重定向
  3. 对于非常规favicon位置(如子目录)也能正确处理
  4. 哈希计算与安全分析工具(如Shodan)兼容
  5. 自动处理相对路径和URL编码问题

你可能感兴趣的:(golang实现从request请求返回的response中提取网站图标的faviconMMH3, faviconMD5, faviconPath, faviconData, faviconURL)