autohotkey操作谷歌浏览器
示例:
#Include Chrome.ahk ; Create an instance of the Chrome class using ; the folder ChromeProfile to store the user profile FileCreateDir, ChromeProfile ChromeInst := new Chrome("ChromeProfile") ; Connect to the newly opened tab and navigate to another website ; Note: If your first action is to navigate away, it may be just as ; effective to provide the target URL when instantiating the Chrome class Tab := ChromeInst.GetTab() Tab.Call("Page.navigate", {"url": "https://autohotkey.com/"}) Tab.WaitForLoad() ; Execute some JavaScript Tab.Evaluate("alert('Hello World!');") ; Close the browser (note: this closes *all* pages/tabs) Tab.Call("Browser.close") ExitApp return
示例2
#NoEnv SetBatchLines, -1 ; --- Create a new chrome instance --- FileCreateDir, ChromeProfile ChromeInst := new Chrome("ChromeProfile", "https://autohotkey.com/") ; --- Connect to the active tab --- Tab := ChromeInst.GetTab() ; --- Perform JavaScript injection --- Loop { InputBox, JS,, ( LTrim Enter some JavaScript to be run in the tab For example: alert('hi'); window.location = "https://p.ahkscript.org/"; ) if ErrorLevel break try Result := Tab.Evaluate(JS) catch e { MsgBox, % "Exception encountered in " e.What ":`n`n" . e.Message "`n`n" . "Specifically:`n`n" . Chrome.Jxon_Dump(Chrome.Jxon_Load(e.Extra), "`t") continue } MsgBox, % "Result: " Chrome.Jxon_Dump(Result) } ExitApp return #include ../Chrome.ahk
Chrome类:
; Chrome.ahk v1.0 ; Copyright GeekDude 2018 ; https://github.com/G33kDude/Chrome.ahk class Chrome { static DebugPort := 9222 ; Escape a string in a manner suitable for command line parameters CliEscape(Param) { return """" RegExReplace(Param, "(\\*)""", "$1$1\""") """" } __New(ProfilePath:="", URL:="about:blank", ChromePath:="", DebugPort:="") { if (ProfilePath != "" && !InStr(FileExist(ProfilePath), "D")) throw Exception("The given ProfilePath does not exist") this.ProfilePath := ProfilePath ; TODO: Perform a more rigorous search for Chrome if (ChromePath == "") FileGetShortcut, %A_StartMenuCommon%\Programs\Google Chrome.lnk, ChromePath if !FileExist(ChromePath) throw Exception("Chrome could not be found") this.ChromePath := ChromePath if (DebugPort != "") { this.DebugPort := Round(DebugPort) if (this.DebugPort <= 0) ; TODO: Support DebugPort of 0 throw Exception("DebugPort must be a positive integer") } ; TODO: Support an array of URLs Run, % this.CliEscape(ChromePath) . " --remote-debugging-port=" this.DebugPort . (ProfilePath ? " --user-data-dir=" this.CliEscape(ProfilePath) : "") . (URL ? " " this.CliEscape(URL) : "") } GetTabs() { http := ComObjCreate("WinHttp.WinHttpRequest.5.1") http.open("GET", "http://127.0.0.1:" this.DebugPort "/json") http.send() return this.Jxon_Load(http.responseText) } GetTab(Index:=0) { ; TODO: Filter pages by type before returning an indexed page if (Index > 0) return new this.Tab(this.GetTabs()[Index]) for Index, Tab in this.GetTabs() if (Tab.type == "page") return new this.Tab(Tab) } class Tab { Connected := False ID := 0 Responses := [] __New(wsurl) { this.BoundKeepAlive := this.Call.Bind(this, "Browser.getVersion",, False) ; TODO: Throw exception on invalid objects if IsObject(wsurl) wsurl := wsurl.webSocketDebuggerUrl wsurl := StrReplace(wsurl, "localhost", "127.0.0.1") this.ws := {"base": this.WebSocket, "_Event": this.Event, "Parent": this} this.ws.__New(wsurl) while !this.Connected Sleep, 50 } Call(DomainAndMethod, Params:="", WaitForResponse:=True) { if !this.Connected throw Exception("Not connected to tab") ; Use a temporary variable for ID in case more calls are made ; before we receive a response. ID := this.ID += 1 this.ws.Send(Chrome.Jxon_Dump({"id": ID , "method": DomainAndMethod, "params": Params})) if !WaitForResponse return ; Wait for the response this.responses[ID] := False while !this.responses[ID] Sleep, 50 ; Get the response, check if it's an error response := this.responses.Delete(ID) if (response.error) throw Exception("Chrome indicated error in response",, Chrome.Jxon_Dump(response.error)) return response.result } Evaluate(JS) { response := this.Call("Runtime.evaluate", ( LTrim Join { "expression": JS, "objectGroup": "console", "includeCommandLineAPI": Chrome.Jxon_True(), "silent": Chrome.Jxon_False(), "returnByValue": Chrome.Jxon_False(), "userGesture": Chrome.Jxon_True(), "awaitPromise": Chrome.Jxon_False() } )) if (response.exceptionDetails) throw Exception(response.result.description,, Chrome.Jxon_Dump(response.exceptionDetails)) return response.result } WaitForLoad(DesiredState:="complete", Interval:=100) { while this.Evaluate("document.readyState").value != DesiredState Sleep, %Interval% } Event(EventName, Event) { ; Called from WebSocket if this.Parent this := this.Parent ; TODO: Handle Error events if (EventName == "Open") { this.Connected := True BoundKeepAlive := this.BoundKeepAlive SetTimer, %BoundKeepAlive%, 15000 } else if (EventName == "Message") { data := Chrome.Jxon_Load(Event.data) if this.responses.HasKey(data.ID) this.responses[data.ID] := data } else if (EventName == "Close") { this.Disconnect() } } Disconnect() { if !this.Connected return this.Connected := False this.ws.Delete("Parent") this.ws.Disconnect() BoundKeepAlive := this.BoundKeepAlive SetTimer, %BoundKeepAlive%, Delete this.Delete("BoundKeepAlive") } class WebSocket { __New(WS_URL) { static wb ; Create an IE instance Gui, +hWndhOld Gui, New, +hWndhWnd this.hWnd := hWnd Gui, Add, ActiveX, vWB, Shell.Explorer Gui, %hOld%: Default ; Write an appropriate document WB.Navigate("about:<!DOCTYPE html><meta http-equiv='X-UA-Compatible'" . "content='IE=edge'><body></body>") while (WB.ReadyState < 4) sleep, 50 this.document := WB.document ; Add our handlers to the JavaScript namespace this.document.parentWindow.ahk_savews := this._SaveWS.Bind(this) this.document.parentWindow.ahk_event := this._Event.Bind(this) this.document.parentWindow.ahk_ws_url := WS_URL ; Add some JavaScript to the page to open a socket Script := this.document.createElement("script") Script.text := "ws = new WebSocket(ahk_ws_url);`n" . "ws.onopen = function(event){ ahk_event('Open', event); };`n" . "ws.onclose = function(event){ ahk_event('Close', event); };`n" . "ws.onerror = function(event){ ahk_event('Error', event); };`n" . "ws.onmessage = function(event){ ahk_event('Message', event); };" this.document.body.appendChild(Script) } ; Called by the JS in response to WS events _Event(EventName, Event) { this["On" EventName](Event) } ; Sends data through the WebSocket Send(Data) { this.document.parentWindow.ws.send(Data) } ; Closes the WebSocket connection Close(Code:=1000, Reason:="") { this.document.parentWindow.ws.close(Code, Reason) } ; Closes and deletes the WebSocket, removing ; references so the class can be garbage collected Disconnect() { if this.hWnd { this.Close() Gui, % this.hWnd ": Destroy" this.hWnd := False } } } } Jxon_Load(ByRef src, args*) { static q := Chr(34) key := "", is_key := false stack := [ tree := [] ] is_arr := { (tree): 1 } next := q . "{[01234567890-tfn" pos := 0 while ( (ch := SubStr(src, ++pos, 1)) != "" ) { if InStr(" `t`n`r", ch) continue if !InStr(next, ch, true) { ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n")) col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1)) msg := Format("{}: line {} col {} (char {})" , (next == "") ? ["Extra data", ch := SubStr(src, pos)][1] : (next == "'") ? "Unterminated string starting at" : (next == "\") ? "Invalid \escape" : (next == ":") ? "Expecting ':' delimiter" : (next == q) ? "Expecting object key enclosed in double quotes" : (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'" : (next == ",}") ? "Expecting ',' delimiter or object closing '}'" : (next == ",]") ? "Expecting ',' delimiter or array closing ']'" : [ "Expecting JSON value(string, number, [true, false, null], object or array)" , ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1] , ln, col, pos) throw Exception(msg, -1, ch) } is_array := is_arr[obj := stack[1]] if i := InStr("{[", ch) { val := (proto := args[i]) ? new proto : {} is_array? ObjPush(obj, val) : obj[key] := val ObjInsertAt(stack, 1, val) is_arr[val] := !(is_key := ch == "{") next := q . (is_key ? "}" : "{[]0123456789-tfn") } else if InStr("}]", ch) { ObjRemoveAt(stack, 1) next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}" } else if InStr(",:", ch) { is_key := (!is_array && ch == ",") next := is_key ? q : q . "{[0123456789-tfn" } else ; string | number | true | false | null { if (ch == q) ; string { i := pos while i := InStr(src, q,, i+1) { val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C") static end := A_AhkVersion<"2" ? 0 : -1 if (SubStr(val, end) != "\") break } if !i ? (pos--, next := "'") : 0 continue pos := i ; update pos val := StrReplace(val, "\/", "/") , val := StrReplace(val, "\" . q, q) , val := StrReplace(val, "\b", "`b") , val := StrReplace(val, "\f", "`f") , val := StrReplace(val, "\n", "`n") , val := StrReplace(val, "\r", "`r") , val := StrReplace(val, "\t", "`t") i := 0 while i := InStr(val, "\",, i+1) { if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0 continue 2 ; \uXXXX - JSON unicode escape sequence xxxx := Abs("0x" . SubStr(val, i+2, 4)) if (A_IsUnicode || xxxx < 0x100) val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6) } if is_key { key := val, next := ":" continue } } else ; number | true | false | null { val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos) ; For numerical values, numerify integers and keep floats as is. ; I'm not yet sure if I should numerify floats in v2.0-a ... static number := "number", integer := "integer" if val is %number% { if val is %integer% val += 0 } ; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo, ; SOMETIMES return strings due to certain optimizations. Since it ; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a else if (val == "true" || val == "false") val := %value% + 0 ; AHK_H has built-in null, can't do 'val := %value%' where value == "null" ; as it would raise an exception in AHK_H(overriding built-in var) else if (val == "null") val := "" ; any other values are invalid, continue to trigger error else if (pos--, next := "#") continue pos += i-1 } is_array? ObjPush(obj, val) : obj[key] := val next := obj==tree ? "" : is_array ? ",]" : ",}" } } return tree[1] } Jxon_Dump(obj, indent:="", lvl:=1) { static q := Chr(34) if IsObject(obj) { static Type := Func("Type") if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "") throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj)) prefix := SubStr(A_ThisFunc, 1, InStr(A_ThisFunc, ".",, 0)) fn_t := prefix "Jxon_True", obj_t := this ? %fn_t%(this) : %fn_t%() fn_f := prefix "Jxon_False", obj_f := this ? %fn_f%(this) : %fn_f%() if (&obj == &obj_t) return "true" else if (&obj == &obj_f) return "false" is_array := 0 for k in obj is_array := k == A_Index until !is_array static integer := "integer" if indent is %integer% { if (indent < 0) throw Exception("Indent parameter must be a postive integer.", -1, indent) spaces := indent, indent := "" Loop % spaces indent .= " " } indt := "" Loop, % indent ? lvl : 0 indt .= indent this_fn := this ? Func(A_ThisFunc).Bind(this) : A_ThisFunc lvl += 1, out := "" ; Make #Warn happy for k, v in obj { if IsObject(k) || (k == "") throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>") if !is_array out .= ( ObjGetCapacity([k], 1) ? %this_fn%(k) : q . k . q ) ;// key . ( indent ? ": " : ":" ) ; token + padding out .= %this_fn%(v, indent, lvl) ; value . ( indent ? ",`n" . indt : "," ) ; token + indent } if (out != "") { out := Trim(out, ",`n" . indent) if (indent != "") out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1) } return is_array ? "[" . out . "]" : "{" . out . "}" } ; Number else if (ObjGetCapacity([obj], 1) == "") return obj ; String (null -> not supported by AHK) if (obj != "") { obj := StrReplace(obj, "\", "\\") , obj := StrReplace(obj, "/", "\/") , obj := StrReplace(obj, q, "\" . q) , obj := StrReplace(obj, "`b", "\b") , obj := StrReplace(obj, "`f", "\f") , obj := StrReplace(obj, "`n", "\n") , obj := StrReplace(obj, "`r", "\r") , obj := StrReplace(obj, "`t", "\t") static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]" while RegExMatch(obj, needle, m) obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0]))) } return q . obj . q } Jxon_True() { static obj := {} return obj } Jxon_False() { static obj := {} return obj } }
混个经验好难啊。
楼主你好,分别用不同版本的AutoHotKey测试示例1,出现如下问题
AutoHotKey.exe v2版本
使用上面chrome类文件,运行示例1
at line 23
Line Text:class Chrome
Error:Function calls require a space or “(” . Use comma only between parameters
21 ; TODO: Perform a more rigorous search for Chrome
22 if (ChromePath == “”)
23 FileGetShortcut, %A_StartMenuCommon%ProgramsGoogle Chrome.lnk, ChromePath
24 if !FileExist(ChromePath)
AutoHotKey.exe 最新版本
使用上面chrome类文件,运行示例1
Error at line 5
Line Text:class Chrome
Error:This line does not contain a recognized action
require a space 空格的问题可以修复,但紧接着有会出现其它问题
已解决
这个chrome.ahk类的github地址:https://github.com/G33kDude/Chrome.ahk
河大佬这个帖子的代码是v1.0,girhub上面已经更新到v1.2了。
使用1.2版本,除了v1.2的chrome.ahk,还需要WebSocket.ahk,JSON.ahk,Jxon.ahk三个文件,都在G33kDude的github上面下载。
楼主好, 这个很好用, 我想问问, 对于已经在chrome中打开的网页, 能获取之作为一个ChromeInst实例, 然后进行js操作吗?
可以的。
v1.0
#Include chrome.ahk
FileCreateDir, ChromeProfile
ChromeInst := new Chrome(“ChromeProfile”)
Tab := ChromeInst.GetTab()
v1.2
#Include chrome.ahk
page := chrome.getpageByTitle(“网页标题”)
测试了一下第一个示例,好像没有成功啊,如何让chrome打开一个网址呢?实在是看不懂啊。
类文件放进去了吗?
你好管理员,我用1.1.28运行这个脚本在win7下使用OK但是在win10下报错是为什么啊?我本来以为是我把Chrome绿化了的问题,但是我取消绿化并且更新到71版本了仍然报错。。。
PS:我申请加群了不知道有没有机会进群学习一下?
请问AutoHotKey.exe运行哪个版本呢