Swift中用URLSession读取YouTube HTML出现十六进制字符,无法解析JSON
解决YouTube网页HTML中JSON转义字符解析问题
问题概述
使用Swift的URLSession请求YouTube网页后,提取<script>标签里的ytInitialData时,发现原本的JSON内容被转换成了十六进制转义字符(如\x7b\x22responseContext\x22),无法直接解析成JSON对象。
解决方案
需要先将这些十六进制转义字符还原为正常的UTF-8字符串,再进行JSON解析。以下是具体实现步骤:
1. 提取包含转义字符的ytInitialData字符串
从HTML中匹配var ytInitialData = '到';之间的内容:
// 假设已获取到htmlString guard let regex = try? NSRegularExpression(pattern: #"var ytInitialData = '(.*?)';?"#, options: .dotMatchesLineSeparators) else { return nil } let matches = regex.matches(in: htmlString, range: NSRange(htmlString.startIndex..., in: htmlString)) guard let match = matches.first, let range = Range(match.range(at: 1), in: htmlString) else { return nil } let escapedJsonString = String(htmlString[range])
2. 解析十六进制转义字符为正常字符串
编写函数将\xXX格式的转义字符转换为对应UTF-8字符:
func unescapeHexString(_ input: String) -> String { var result = "" var index = input.startIndex while index < input.endIndex { if input[index] == "\\" && index != input.index(before: input.endIndex) && input[input.index(after: index)] == "x" { // 提取\x后的两位十六进制字符 let hexStart = input.index(index, offsetBy: 2) guard hexStart < input.endIndex, let hexEnd = input.index(hexStart, offsetBy: 2, limitedBy: input.endIndex) else { result.append(input[index]) index = input.index(after: index) continue } let hexStr = String(input[hexStart..<hexEnd]) if let hexValue = UInt8(hexStr, radix: 16) { result.append(Character(UnicodeScalar(hexValue))) } else { // 解析失败则保留原字符 result.append("\(input[index])\(input[input.index(after: index)])\(hexStr)") } index = hexEnd } else { result.append(input[index]) index = input.index(after: index) } } return result } // 转换转义字符串 let rawJsonString = unescapeHexString(escapedJsonString)
3. 解析JSON为对象
将还原后的字符串解析为JSON对象:
do { let jsonData = rawJsonString.data(using: .utf8)! let ytInitialData = try JSONSerialization.jsonObject(with: jsonData, options: []) // 处理解析后的JSON数据 print(ytInitialData) } catch { print("JSON解析失败: \(error.localizedDescription)") }
完整整合代码
将上述步骤整合到原有请求逻辑中:
func fetchYouTubeData(url: URL) async -> Any? { var request = URLRequest(url: url) guard let (data, response) = try? await URLSession.shared.data(from: url) else { return nil } guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { return nil } guard let htmlString = String(data: data, encoding: .utf8) else { return nil } // 提取转义的JSON字符串 guard let regex = try? NSRegularExpression(pattern: #"var ytInitialData = '(.*?)';?"#, options: .dotMatchesLineSeparators) else { return nil } let matches = regex.matches(in: htmlString, range: NSRange(htmlString.startIndex..., in: htmlString)) guard let match = matches.first, let range = Range(match.range(at: 1), in: htmlString) else { return nil } let escapedJsonString = String(htmlString[range]) // 还原转义字符 func unescapeHexString(_ input: String) -> String { var result = "" var index = input.startIndex while index < input.endIndex { if input[index] == "\\" && index != input.index(before: input.endIndex) && input[input.index(after: index)] == "x" { let hexStart = input.index(index, offsetBy: 2) guard hexStart < input.endIndex, let hexEnd = input.index(hexStart, offsetBy: 2, limitedBy: input.endIndex) else { result.append(input[index]) index = input.index(after: index) continue } let hexStr = String(input[hexStart..<hexEnd]) if let hexValue = UInt8(hexStr, radix: 16) { result.append(Character(UnicodeScalar(hexValue))) } else { result.append("\(input[index])\(input[input.index(after: index)])\(hexStr)") } index = hexEnd } else { result.append(input[index]) index = input.index(after: index) } } return result } let rawJsonString = unescapeHexString(escapedJsonString) // 解析JSON do { let jsonData = rawJsonString.data(using: .utf8)! return try JSONSerialization.jsonObject(with: jsonData, options: []) } catch { print("JSON解析失败: \(error)") return nil } }
说明
- 正则表达式需根据实际返回的HTML结构微调,确保精准匹配
ytInitialData内容 - 转义字符处理函数会遍历字符串,逐个解析
\xXX格式的转义序列 - 解析JSON时可替换为
Codable模型,更便捷地访问数据
内容的提问来源于stack exchange,提问作者sumit kumar Pradhan
相关产品推荐
相关产品推荐

