You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift解析Google网页报错:无法确定文本编码的解决办法

问题解决:加载Google域名网页时编码检测失败的报错

问题场景

使用Swift代码解析网页内容时,访问普通网站(如https://hello.com)正常,但访问Google相关域名(如https://google.com)时,抛出错误:The file couldn’t be opened because the text encoding of the contents couldn’t be determined。原代码使用String(contentsOf:)同步加载网页内容,结合SwiftSoup解析,再通过自定义扩展将HTML转为字符串。

问题原因

  1. 编码不匹配:Google返回的网页内容编码可能并非UTF-8,String(contentsOf:)自动检测编码时失败,导致无法正确转换为字符串。
  2. 同步请求风险:String(contentsOf:)是同步网络请求,不仅会阻塞主线程,且对编码的自动处理能力有限,无法应对复杂的响应编码情况。
  3. 强制try!掩盖错误:原代码大量使用try!,会直接崩溃而无法捕获和处理编码相关的异常。

解决方案

1. 改用URLSession异步请求,按响应头指定编码解析内容

通过URLSession发起异步请求,从响应头的Content-Type字段提取编码格式,再用对应编码将Data转为字符串,避免自动检测编码失败的问题。

2. 替换强制try!为do-catch错误处理

合理捕获解析过程中的异常,避免程序直接崩溃,同时便于排查问题。

3. 可选:直接用SwiftSoup提取文本,跳过HTML转字符串步骤

如果只是需要网页文本内容,可直接通过SwiftSoup的text()方法提取,无需再通过NSAttributedString转换,效率更高。

修改后的代码示例

import UIKit
import SwiftSoup

class ViewController: UIViewController {
    
    @IBOutlet weak var ingredientsLabel: UILabel! // 修正变量名拼写
    
    override func viewDidLoad() {
        super.viewDidLoad()
        fetchAndParseWebContent()
    }
    
    private func fetchAndParseWebContent() {
        guard let url = URL(string: "https://google.com") else {
            ingredientsLabel.text = "无效URL"
            return
        }
        
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard let self = self else { return }
            
            // 处理网络错误
            if let error = error {
                DispatchQueue.main.async {
                    self.ingredientsLabel.text = "请求失败:\(error.localizedDescription)"
                }
                return
            }
            
            guard let data = data, let response = response as? HTTPURLResponse else {
                DispatchQueue.main.async {
                    self.ingredientsLabel.text = "无有效响应数据"
                }
                return
            }
            
            // 从响应头获取编码格式
            var encoding: String.Encoding = .utf8
            if let contentType = response.allHeaderFields["Content-Type"] as? String {
                let charsetRegex = try! NSRegularExpression(pattern: "charset=([^;]+)", options: .caseInsensitive)
                if let match = charsetRegex.firstMatch(in: contentType, range: NSRange(contentType.startIndex..., in: contentType)) {
                    let charsetRange = Range(match.range(at: 1), in: contentType)!
                    let charset = String(contentType[charsetRange])
                    encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding(charset as CFString)))
                }
            }
            
            // 按指定编码转换为字符串
            guard let content = String(data: data, encoding: encoding) else {
                DispatchQueue.main.async {
                    self.ingredientsLabel.text = "编码解析失败"
                }
                return
            }
            
            // 使用SwiftSoup解析内容
            do {
                let doc = try SwiftSoup.parse(content)
                guard let body = doc.select("body").first() else {
                    DispatchQueue.main.async {
                        self.ingredientsLabel.text = "未找到body内容"
                    }
                    return
                }
                
                // 方案1:直接用SwiftSoup提取文本
                let textContent = try body.text()
                DispatchQueue.main.async {
                    self.ingredientsLabel.text = textContent
                }
                
                // 方案2:如果仍需要HTML转字符串,使用原扩展(可选)
                /*
                let htmlContent = try body.html()
                let convertedText = htmlContent.HtmlToString ?? "HTML转换失败"
                DispatchQueue.main.async {
                    self.ingredientsLabel.text = convertedText
                }
                */
                
            } catch {
                DispatchQueue.main.async {
                    self.ingredientsLabel.text = "解析失败:\(error.localizedDescription)"
                }
            }
        }
        task.resume()
    }
}

extension String {
    var HtmlToString: String? {
        guard let data = data(using: .utf8) else { return nil }
        do {
            return try NSAttributedString(data: data, options: [
                .documentType: NSAttributedString.DocumentType.html,
                .characterEncoding: String.Encoding.utf8.rawValue
            ], documentAttributes: nil).string
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }
}

关键修改点说明

  • 异步请求:用URLSession替代String(contentsOf:),避免主线程阻塞,同时能获取响应头信息。
  • 编码处理:从响应头的Content-Type中提取charset,转换为对应的String.Encoding,确保字符串转换准确。
  • 错误处理:新增网络错误、数据为空、解析失败等场景的处理,避免程序崩溃。
  • 变量名修正:将Ingrediants改为ingredientsLabel,符合Swift命名规范。

内容的提问来源于stack exchange,提问作者Aakarsh Tathachar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:50:28