WKWebview获取并修改网页中图片地址的两种方法

做这个需求的原因比较蛋疼,因为新闻http协议经常被第三方注入广告,导致新闻中经常出现抽奖广告什么的,产品将新闻替换为https协议,但是有的图片还是http请求,无法通过证书校验(是说无法校验,我还需要验证下)

一、使用js注入进行图片url替换

基本想法就是页面加载完成后注入一段js,然后找到所有标签,替换src地址。

这种方法有个问题,需要等网页加载完成之后才能去替换,这样有可能之前的http图片已经加载了(但是没有通过校验,加载不了),然后再加载你设置的图片,导致加载多次,浪费流量

    override func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        super.webView(webView, didFinish: navigation)
        let jsString = """
            function replaceHttpImageSrc() {
                var imgs = document.getElementsByTagName("img");
                var urlArray = [];
                for (var i = 0; i < imgs.length; i++) {
                    var src = imgs[i].src;
                    if (src.indexOf("http://") != -1 && src.indexOf("https://") == -1) {
                        var newSrc = src.replace("http://","");
                        var newImgSrc = "你需要替换的图片地址";
                        var imgSrc = imgs[i].setAttribute("src", newImgSrc);
                    }
                }
            }
        """
        
        // 执行js,如果有错,不进行下一步处理
        webView.evaluateJavaScript(jsString) { (info, error) in
            
            LOG.debug("注入js函数成功 jsString: \(jsString)")
            
            if error == nil {
                webView.evaluateJavaScript("replaceHttpImageSrc()", completionHandler: { (resutl, error) in
                    if error == nil {
                        LOG.debug("执行url中图片链接替换成功")
                    }
                })
            }
            
        }
    }

二、使用URLProtocol接管webview中所有的图片请求

这种方式需要使用到私有api,可以使用字符拼接或者base64解密的方式隐藏掉字符串,但是可能会碰到私有api弃用或者名称改变的情况,不建议使用

1、自建URLProtocol,处理需要的请求。我的名称为CustomURLProtocol
在app启动或者建立vc的时候注册协议

URLProtocol.registerClass(CustomURLProtocol.self)

2、获取私有类,注册需要监听的请求类型
为啥是这两个私有类,可以看下文章https://www.jianshu.com/p/8f5e1082f5e0,或者直接搜这个类的名字

        let cls:AnyClass? = NSClassFromString("WKBrowsingContextController")
        let sel = NSSelectorFromString("registerSchemeForCustomProtocol:")

        if let mycls = cls, mycls.responds(to: sel) {
            // 实例方法
//            let obj = (mycls as! NSObject.Type).init()
//            obj.perform(sel, with: "http")
//            obj.perform(sel, with: "https")
            // 静态方法
            (mycls as! NSObject.Type).perform(sel, with: "http")
            (mycls as! NSObject.Type).perform(sel, with: "https")
        }

3、利用注册的CustomURLProtocol处理所有的http和https请求

import Foundation
import UIKit
import WebKit
import Alamofire

open class CustomURLProtocol: URLProtocol {
    
    static let imageTypes = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".psd", ".svg", ".swf", ".tiff", ".tif", "=jpeg"]
    
    
    /// 处理哪些请求需要接管
    ///
    /// - Parameter request: 当前请求request
    /// - Returns: true代表有当前协议接管,false就由系统接管
    open override class func canInit(with request: URLRequest) -> Bool {
        // 只处理http和https请求  ,我的需求只处理http
        if let scheme = request.url?.scheme {
            if scheme == "http" || scheme == "https" {
                
                // 已经处理过的不再处理
                if let pre = URLProtocol.property(forKey: "kURLProtocolHandledKey", in: request) as? Bool, pre {
                    return false
                } else {
                    return true
                }
            }
        }
        
        return false
    }
    
    
    /// 这里可以将请求进行封装,比如加上统一的请求头,替换某些请求地址
    ///
    /// - Parameter request: 当前请求
    /// - Returns: 新的请求
    open override class func canonicalRequest(for request: URLRequest) -> URLRequest {
//        print(request.url?.absoluteString ?? "")
        
        guard let url = request.url else {
            return request
        }
        
        let urlString = url.absoluteString
        
        // 包含图片文件,替换图片为指定的图片,也可以增加额外的参数
        for type in imageTypes {
            if urlString.lowercased().contains(type) {
                if let url = URL(string: "https://ws2.sinaimg.cn/large/006tNc79gy1fo6k80vn82j3087036wem.jpg") {
                    let mutableRequest = NSMutableURLRequest(url: url, cachePolicy: request.cachePolicy, timeoutInterval: request.timeoutInterval)
                    return mutableRequest as URLRequest
                }
            }
        }
        
        return request
        
    }
    
    
    var session: URLSession?
    var dataTask: URLSessionDataTask?
    
    /// 开始加载请求
    open override func startLoading() {
        
//        print("start loading")
        
        guard let url = self.request.url else {
            return
        }
        
//        UserDefaults.standard.set(true, forKey: "kURLProtocolHandledKey"+ url.absoluteString)
        URLProtocol.setProperty(true, forKey: "kURLProtocolHandledKey", in: NSMutableURLRequest(url: url))
    
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notification.request.url.loading"), object: url.absoluteString)
        
        let config = URLSessionConfiguration.default
        self.session = URLSession(configuration: config, delegate: self , delegateQueue: self.queue)
        self.dataTask = self.session?.dataTask(with: self.request)          // 需要使用本身的request
        self.dataTask?.resume()
        
    }
    
    let queue = OperationQueue()
    
    /// 结束加载请求
    open override func stopLoading() {
//        print("end loading")
        
        self.session?.invalidateAndCancel()
        self.session = nil
        
//        self.dataTask?.cancel()
        
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notification.request.url.loading"), object: "")
        
    }
    
    
}

extension CustomURLProtocol: URLSessionDataDelegate {
    
    // 完成请求
    public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let myerror = error {
            self.client?.urlProtocol(self, didFailWithError: myerror)
        } else {
            self.client?.urlProtocolDidFinishLoading(self)
        }
    }
    
    public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: URLCache.StoragePolicy.notAllowed)
        completionHandler(URLSession.ResponseDisposition.allow)
    }
    
    public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        self.client?.urlProtocol(self, didLoad: data)
    }
    
    public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
        completionHandler(proposedResponse)
    }
    
    // 重定向url
    public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {

        guard let url = request.url else {
            return
        }
        let redirectRequest = NSMutableURLRequest(url: url)
        URLProtocol.removeProperty(forKey: "kURLProtocolHandledKey", in: redirectRequest)
        
        self.client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response)

        // 取消当前任务
        self.dataTask?.cancel()

        // 取消当前的加载
        self.client?.urlProtocol(self, didFailWithError: NSError(domain: "error domain", code: NSUserCancelledError, userInfo: nil))

    }

}

另一个文件自行修改下

import Foundation
import WebKit

// 用来拦截url中的所有请求

class WKProtocolTestVC: USBaseViewController {
    
    var webview: WKWebView!
    
    override func setup() {
        super.setup()
        
        URLProtocol.registerClass(CustomURLProtocol.self)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "WKWebview 处理图片"
        
        if let url = URL(string: "https://www.baidu.com") {
            let request = URLRequest(url: url)
            webview.load(request)
        }
    }
    
    override func buildUI() {
        super.buildUI()
        
        let config = WKWebViewConfiguration()
        
        self.webview = WKWebView(frame: CGRect.zero, configuration: config)
        self.webview.navigationDelegate = self
        self.webview.uiDelegate = self
        self.view.addSubview(webview)
        
        
        webview.snp.makeConstraints { (make) in
            make.edges.equalToSuperview()
        }
        
        let cls:AnyClass? = NSClassFromString("WKBrowsingContextController")
        let sel = NSSelectorFromString("registerSchemeForCustomProtocol:")
        
        print(cls)
        print(sel)
        
        if let mycls = cls, mycls.responds(to: sel) {
            
            // 实例方法
//            let obj = (mycls as! NSObject.Type).init()
//            obj.perform(sel, with: "http")
//            obj.perform(sel, with: "https")
            
            // 静态方法
            (mycls as! NSObject.Type).perform(sel, with: "http")
            (mycls as! NSObject.Type).perform(sel, with: "https")
            
            
            
        }
        
    }

}

extension WKProtocolTestVC: WKNavigationDelegate, WKUIDelegate {
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        let jsString = """
            function replaceHttpImageSrc() {
                var imgs = document.getElementsByTagName("img");
                var urlArray = [];
                for (var i = 0; i < imgs.length; i++) {
                    var src = imgs[i].src;
                    if (src.indexOf("http://") != -1 && src.indexOf("https://") == -1) {
                        var newSrc = src.replace("http://","");
                        var newImgSrc = "你需要替换的图片地址";
                        var imgSrc = imgs[i].setAttribute("src", newImgSrc);
                    }
                }
            }
        """
        
        // 执行js,如果有错,不进行下一步处理
        webView.evaluateJavaScript(jsString) { (info, error) in
            
            print("注入js函数成功 jsString: \(jsString)")
            
            if error == nil {
                webView.evaluateJavaScript("replaceHttpImageSrc()", completionHandler: { (resutl, error) in
                    if error == nil {
                        print("执行url中图片链接替换成功")
                    }
                })
            }
            
        }
    }
    
    
}

你可能感兴趣的:(WKWebview获取并修改网页中图片地址的两种方法)