日后,網絡爬蟲也好,數據采集也好,自動化必然是主流。因此,筆者未雨綢繆,在此研究各類自動化源碼,希望能夠趕上時代,做出一套實用的自動化框架。
這里先研究傳統的webdriver中轉來進行瀏覽器自動化的源碼。
webdriver官方位于這里:WebDriver
用過selenium的同學應該都知道,需要有selenium這個自動化庫來寫腳本,需要一個webdriver.exe,還需要一個瀏覽器。流程基本如下:
編寫腳本----->發送消息給webdriver------>webdriver發消息給瀏覽器
通過這樣一個流程,就完成了自動化。
腳本簡單,資料一大堆,瀏覽器也只是個執行者,所以關鍵在于webdriver如何接受、處理、發送消息,所以重點源碼webdriver源碼。
WebDriverMain.cpp:啟動函數
#include "config.h"#include "LogInitialization.h"
#include "WebDriverService.h"
#include <wtf/MainThread.h>
#include <wtf/Threading.h>#if OS(ANDROID)
__attribute__((visibility("default")))
int WebKit::WebDriverProcessMain(int argc, char** argv)
#else
int main(int argc, char** argv)
#endif
{WebDriver::WebDriverService::platformInit();WTF::initializeMainThread();
#if !LOG_DISABLED || !RELEASE_LOG_DISABLEDWebDriver::logChannels().initializeLogChannelsIfNecessary(WebDriver::logLevelString());
#endifWebDriver::WebDriverService service;return service.run(argc, argv);
}
步驟 1:平臺初始化:platformInit源碼里空白無實現,應該是等之后更新。
步驟 2:WTF(Web Template Framework)主線程初始化
步驟 3:日志通道初始化
步驟 4:創建并運行 WebDriver 服務
WebDriverService.cpp:解析命令參數,進入監聽loop循環。。
if (const char* targetEnvVar = getenv("WEBDRIVER_TARGET_ADDR"))targetString = String::fromLatin1(targetEnvVar);
先獲取一個名為WEBDRIVER_TARGET_ADDR的環境變量,這個變量是用于鏈接已經開啟的瀏覽器,是的,需要先把瀏覽器打開,然后webdriver回去鏈接,而不是先運行webdriver。
然后解析命令行參數,一大堆,沒什么可看的:
if (equalSpans(arg, "-h"_span) || equalSpans(arg, "--help"_span)) {printUsageStatement(argv[0]);return EXIT_SUCCESS;}if (equalSpans(arg, "-p"_span) && portString.isNull()) {if (++i == argc) {printUsageStatement(argv[0]);return EXIT_FAILURE;}portString = String::fromLatin1(argv[i]);continue;}static constexpr auto portArgument = "--port="_span;if (spanHasPrefix(arg, portArgument) && portString.isNull()) {portString = arg.subspan(portArgument.size());continue;}static constexpr auto hostArgument = "--host="_span;if (spanHasPrefix(arg, hostArgument) && !host) {host = arg.subspan(hostArgument.size());continue;}#if ENABLE(WEBDRIVER_BIDI)static constexpr auto bidiPortArgument = "--bidi-port="_span;if (spanHasPrefix(arg, bidiPortArgument) && bidiPortString.isNull()) {bidiPortString = arg.subspan(bidiPortArgument.size());continue;}
#endifif (equalSpans(arg, "-t"_span) && targetString.isNull()) {if (++i == argc) {printUsageStatement(argv[0]);return EXIT_FAILURE;}targetString = String::fromLatin1(argv[i]);continue;}static constexpr auto targetArgument = "--target="_span;if (spanHasPrefix(arg, targetArgument) && targetString.isNull()) {targetString = arg.subspan(targetArgument.size());continue;}if (equalSpans(arg, "--replace-on-new-session"_span)) {m_replaceOnNewSession = true;continue;}}if (portString.isNull()) {printUsageStatement(argv[0]);return EXIT_FAILURE;}if (!targetString.isEmpty()) {auto position = targetString.reverseFind(':');if (position != notFound) {m_targetAddress = targetString.left(position);m_targetPort = parseIntegerAllowingTrailingJunk<uint16_t>(StringView { targetString }.substring(position + 1)).value_or(0);}}auto port = parseInteger<uint16_t>(portString);if (!port) {fprintf(stderr, "Invalid port %s provided\n", portString.utf8().data());return EXIT_FAILURE;}#if ENABLE(WEBDRIVER_BIDI)auto bidiPort = parseInteger<uint16_t>(bidiPortString);if (!bidiPort) {const int16_t bidiPortIncrement = *port == std::numeric_limits<uint16_t>::max() ? -1 : 1;bidiPort = { *port + bidiPortIncrement };fprintf(stderr, "Invalid WebSocket BiDi port %s provided. Defaulting to %d.\n", bidiPortString.utf8().data(), *bidiPort);}
#endif
最后是進入主循環,這里先線程初始化,然后看是websockst還是http模式,前者雙向,后者單向。之后如果監聽listen成功,就進入loop的循環不斷接受消息:
WTF::initializeMainThread();const char* hostStr = host && host->utf8().data() ? host->utf8().data() : "local";
#if ENABLE(WEBDRIVER_BIDI)if (!m_bidiServer.listen(host ? *host : nullString(), *bidiPort)) {fprintf(stderr, "FATAL: Unable to listen for WebSocket BiDi server at host %s and port %d.\n", hostStr, *bidiPort);return EXIT_FAILURE;}RELEASE_LOG(WebDriverBiDi, "Started WebSocket BiDi server with host %s and port %d", hostStr, *bidiPort);
#endif // ENABLE(WEBDRIVER_BIDI)if (!m_server.listen(host, *port)) {fprintf(stderr, "FATAL: Unable to listen for HTTP server at host %s and port %d.\n", hostStr, *port);return EXIT_FAILURE;}RELEASE_LOG(WebDriverClassic, "Started HTTP server with host %s and port %d", hostStr, *port);RunLoop::run();#if ENABLE(WEBDRIVER_BIDI)m_bidiServer.disconnect();
#endifm_server.disconnect();return EXIT_SUCCESS;
其中的監聽代碼如下:
bool HTTPServer::listen(const std::optional<String>& host, unsigned port)
{auto& endpoint = RemoteInspectorSocketEndpoint::singleton();if (auto id = endpoint.listenInet(host ? host.value().utf8().data() : "", port, *this)) {m_server = id;return true;}return false;
}
這里先用了個設計模式:單例模式,為了可復用利用同一個監聽端點。
然后是很常規的server模式,打開一個TCP端口監聽。
std::optional<ConnectionID> RemoteInspectorSocketEndpoint::listenInet(const char* address, uint16_t port, Listener& listener)
{Locker locker { m_connectionsLock };auto id = generateConnectionID();auto connection = makeUnique<ListenerConnection>(id, listener, address, port);if (!connection->isListening())return std::nullopt;m_listeners.add(id, WTFMove(connection));wakeupWorkerThread();return id;
}bool RemoteInspectorSocketEndpoint::isListening(ConnectionID id)
{Locker locker { m_connectionsLock };if (m_listeners.contains(id))return true;return false;
}
主循環代碼如下,典型的一個GUI的事件消息處理機制,熟悉win32的同學應該很懂,而且這里就是純粹的win32的函數接口,獲取消息,把消息轉化為字符消息,分發消息:
void RunLoop::run()
{MSG message;while (BOOL result = ::GetMessage(&message, nullptr, 0, 0)) {if (result == -1)break;::TranslateMessage(&message);::DispatchMessage(&message);}
}