web_driver.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. const net = require('net')
  2. const CDP = require('chrome-remote-interface')
  3. const chromeFinder = require('chrome-finder')
  4. const {
  5. exec,
  6. spawn,
  7. } = require('child_process')
  8. const path = require('node:path')
  9. const os = require('node:os')
  10. class WebDriver {
  11. constructor (params = {
  12. args: [],
  13. host: '127.0.0.1',
  14. port: 0,
  15. chromePath: '',
  16. userDataDir: '',
  17. }) {
  18. this.args = params.args || []
  19. this.host = params.host || '127.0.0.1'
  20. this.port = params.port || 0
  21. this.chromePath = params.chromePath || ''
  22. this.userDataDir = params.userDataDir || ''
  23. }
  24. headless () {
  25. this.args.push('--headless=old')
  26. }
  27. getCDP () {
  28. return this.cdp
  29. }
  30. setProxy (proxy) {
  31. this.args.push(`--proxy-server=${proxy}`)
  32. }
  33. addArguments (arg) {
  34. if (arg) {
  35. this.args.push(arg)
  36. } else {
  37. throw new Error('Arguments cannot be null')
  38. }
  39. }
  40. setChromeBinaryPath (binaryPath) {
  41. this.chromePath = binaryPath
  42. }
  43. setUserDataDirPath (binaryPath) {
  44. this.userDataDir = binaryPath
  45. }
  46. async builder () {
  47. try {
  48. if (!this.port) {
  49. this.port = await this.isPortTaken(this.getRandomNumber())
  50. }
  51. if (!this.chromePath) {
  52. this.chromePath = chromeFinder()
  53. }
  54. if (!this.userDataDir) {
  55. this.userDataDir = path.join(os.tmpdir(), `scoped_dir${new Date().getTime()}`)
  56. }
  57. console.info('启动端口:', this.port)
  58. const command = `${this.chromePath}`
  59. const args = [
  60. `--user-data-dir=${this.userDataDir}`,
  61. `--disk-cache-dir=${this.userDataDir}`,
  62. `--remote-debugging-port=${this.port}`,
  63. '--no-first-run',
  64. '--no-dleck',
  65. '--disable-plugins',
  66. '--no-default-browser-check',
  67. '--new-tab',
  68. // 'data:,',
  69. ]
  70. const mergedArray = args.concat(this.args)
  71. const { spawn } = require('child_process')
  72. return new Promise((resolve, reject) => {
  73. const childProcess = spawn(command, mergedArray)
  74. childProcess.stderr.on('data', async (data) => {
  75. await this.connectToChrome(1, this.port)
  76. resolve(true)
  77. })
  78. childProcess.on('close', (code) => {
  79. console.info(`子进程退出成功,退出码: ${code}`)
  80. })
  81. childProcess.on('error', (err) => {
  82. console.error(`子进程错误:${err.message}`)
  83. })
  84. })
  85. } catch (e) {
  86. console.error(e)
  87. }
  88. }
  89. createCDPConnection () {
  90. return {
  91. host: this.host,
  92. port: this.port,
  93. }
  94. }
  95. checkBrowser () {
  96. return !!(this.chromePath || chromeFinder())
  97. }
  98. isPortTaken (port) {
  99. return new Promise((resolve) => {
  100. const server = net.createServer()
  101. server.on('listening', () => {
  102. server.close(() => {
  103. resolve(port)
  104. })
  105. })
  106. server.on('error', async () => {
  107. this.isPortTaken(port + 1).then(resolve)
  108. })
  109. server.listen(port, '127.0.0.1')
  110. })
  111. }
  112. async connectToChrome (time, port) {
  113. const waitingTime = {
  114. 1: 1000,
  115. 2: 3000,
  116. 3: 5000,
  117. 4: 10000,
  118. }
  119. if (!this.cdp) {
  120. try {
  121. this.cdp = await CDP({ port })
  122. console.info('CDP 连接成功')
  123. } catch (error) {
  124. console.error('连接失败,正在重试...')
  125. await new Promise(resolve => setTimeout(resolve, waitingTime[time]))
  126. if (time < 4) {
  127. await this.connectToChrome(time + 1, port)
  128. } else {
  129. await this.quit()
  130. await this.builder()
  131. }
  132. }
  133. }
  134. }
  135. getRandomNumber () {
  136. return Math.floor(Math.random() * (65535 - 10000 + 1)) + 10000
  137. }
  138. quit () {
  139. const command = `netstat -ano | findstr LISTENING | findstr :${this.port}`
  140. return new Promise((resolve, reject) => {
  141. exec(command, (error, stdout, stderr) => {
  142. if (error) {
  143. resolve(`执行命令时出错: ${error}`)
  144. return
  145. }
  146. const lines = stdout.split('\n')
  147. const processIds = []
  148. lines.forEach((line) => {
  149. const parts = line.trim().split(/\s+/)
  150. if (parts.length >= 2) {
  151. const pid = parts[parts.length - 1]
  152. processIds.push(pid)
  153. }
  154. })
  155. if (processIds.length === 0) {
  156. resolve(`未找到在端口 ${this.port} 上运行的进程`)
  157. return
  158. }
  159. if (processIds.length > 0) {
  160. processIds.forEach((pid) => {
  161. exec(`taskkill /F /PID ${pid}`, (error) => {
  162. if (error) {
  163. resolve(`关闭进程时出错: ${error}`)
  164. } else {
  165. resolve(this.port)
  166. }
  167. })
  168. })
  169. }
  170. })
  171. })
  172. }
  173. }
  174. module.exports = { WebDriver: WebDriver }