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

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

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

基本想法就是页面加载完成后注入一段js,然后找到所有<img>标签,替换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中图片链接替换成功")
                    }
                })
            }
            
        }
    }
    
    
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,831评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 内观滋长表: 1.发生了什么? 周日晚上儿子给我说“妈妈你不是说谁也不能管谁吗?” 我说“恩” 儿子说“那我明天不...
    夏山自由阅读 381评论 0 0
  • 从雷州茂德公古城的文化元素到传统文化的承传 作者/弟哥 2016年8月23日,雷州茂德公古城的开城,在雷州这块红土...
    弟哥阅读 2,295评论 0 1
  • 昨晚凌晨三点睡着,直接导致下午一点才起床。算是补充了周一至周六睡眠的缺乏。起来后直接去吃了碗面。这个点餐厅刚刚好。...
    化浊阅读 213评论 1 0