关键词搜索

源码搜索 ×
×

紫鸟超级浏览器-SeleniumAPI通信

发布2021-09-02浏览1911次

详情内容

超级浏览器Webdriver自动化开发

一、概述

通过Webdriver实现对超级浏览器内的店铺进行,自动化控制以及数据抓取,主要流程分为以下两个部分

(一)与超级浏览器主进程通信。

这个部分是通过Socket实现与超级浏览器主进实现通讯的,主要工作是获取店铺列表以及准备店铺环境,一个店铺相当于一个独立浏览器。

  1. import json
  2. import subprocess
  3. from socket import *
  4. from selenium import webdriver
  5. from db.db_redis import DBRedis
  6. from common.utility import Utility
  7. from common.mapping import Mapping
  8. from common.global_logger import logger
  9. from selenium.webdriver import ActionChains
  10. from selenium.common.exceptions import NoSuchElementException
  11. class SuperBrowser(object):
  12. # 基础配置
  13. utils = Utility()
  14. config = utils.confg
  15. # 初始化Redis服务
  16. obj_redis = DBRedis()
  17. # 获取业务类型
  18. business_type = config.get('business_type')
  19. logger.info("business_type: %s" % business_type)
  20. # 指定使用英语
  21. __LANGUAGE = config.get('language')
  22. # ----------------------------------------->> Socket通信地址端口
  23. host = config.get('socket_host')
  24. port = int(config.get('socket_port'))
  25. logger.info('socket > host: %s, port: %s' % (host, port))
  26. # ----------------------------------------->> 请求紫鸟超级浏览器API方法
  27. __GET_BROWSER_LIST = "getBrowserList" # 获取店铺列表
  28. __START_BROWSER = "startBrowser" # 启动店铺(主程序)
  29. __STOP_BROWSER = "stopBrowser" # 关闭店铺窗口
  30. __GET_BROWSER_ENV_INFO = "getBrowserEnvInfo" # 启动店铺(webdriver)
  31. __HEARTBEAT = "heartbeat" # 非必要接口,只是用于保活Socket连接
  32. __EXIT = "exit" # 正常退出超级浏览器主进程,会自动关闭已启动店铺并保持店铺cookie等信息。
  33. def __init__(self):
  34. logger.info("初始化Socket连接...")
  35. logger.info("启动紫鸟浏览器......")
  36. self.buf_size = int(self.config.get('socket_buf_size'))
  37. self.IS_HEADLESS = self.config.get('browser_is_headless') # 浏览器是否启用无头模式 false 否、true
  38. # 获取紫鸟·超级浏览器安装路径
  39. path_super_browser = self.config.get('path_super_browser')
  40. cmd = "{} --run_type=web_driver --socket_port={}".format(path_super_browser, self.port)
  41. subprocess.Popen(cmd)
  42. try:
  43. # ------------------------------创建套接字通道
  44. self.address = (self.host, self.port)
  45. self.tcpCliSock = socket(AF_INET, SOCK_STREAM) # 创建套接字
  46. self.tcpCliSock.connect(self.address) # 主动初始化TCP服务器连接
  47. except ConnectionRefusedError as e:
  48. logger.error(e)
  49. subprocess.Popen('taskkill /f /im superbrowser.exe')
  50. except Exception as e:
  51. logger.error(e)
  52. def browser_api(self, action, args=None):
  53. """
  54. 紫鸟·超级浏览器API
  55. :param action: 方法
  56. :param args: 可选参数
  57. :return:
  58. """
  59. REQUEST_ID = "0123456789" # 全局唯一标识
  60. user_info = json.dumps({ # 用户信息
  61. "company": self.config.get('browser_company_name'),
  62. "username": self.config.get('browser_username'),
  63. "password": self.config.get('browser_password')
  64. })
  65. # 默认为获取店铺列表
  66. common = {"userInfo": user_info, "action": self.__GET_BROWSER_LIST, "requestId": REQUEST_ID}
  67. if action == self.__START_BROWSER or action == self.__GET_BROWSER_ENV_INFO or action == self.__STOP_BROWSER:
  68. common['browserOauth'] = args['browserOauth']
  69. common['isHeadless'] = args['isHeadless']
  70. common['action'] = action
  71. return common
  72. def socket_communication(self, params):
  73. """
  74. Socket通信
  75. :param params: 参数对象
  76. :return:
  77. """
  78. try:
  79. args = (str(params) + '\r\n').encode('utf-8')
  80. # 将 string 中的数据发送到连接的套接字
  81. self.tcpCliSock.send(args)
  82. # 接收的最大数据量
  83. res = self.tcpCliSock.recv(self.buf_size)
  84. return json.loads(res)
  85. except ConnectionResetError as e:
  86. logger.warning("ConnectionResetError: %s" % e)
  87. logger.info("socket 连接已关闭")
  88. except Exception as e:
  89. logger.error("socket_communication error: %s" % e)
  90. pass
  91. # 举个栗子?
  92. def browser_list(self):
  93. """
  94. 获取店铺列表
  95. 这里采用Redis管理店铺,为了后期分布式部署准备。
  96. :return:
  97. """
  98. logger.info("")
  99. logger.info("获取店铺列表.")
  100. shop_list_params = self.browser_api(self.__GET_BROWSER_LIST)
  101. shop_info = self.socket_communication(shop_list_params)
  102. if shop_info['statusCode'] == 0:
  103. browser_size = len(shop_info['browserList'])
  104. logger.info("目前店铺总数: %s, 正在记录店铺信息...,请稍等." % browser_size)
  105. current_time = Utility.curr_time()
  106. for index, browser in enumerate(shop_info['browserList']):
  107. index += 1
  108. # site_id 对应的值
  109. browser['site_name'] = Mapping.SiteIdExplain(browser['siteId'])
  110. browserOauth = browser['browserOauth']
  111. if browser['isExpired'] is False:
  112. # 记录店铺的数据
  113. key_completed = self.config.get('r_amz_shops_completed')
  114. key_inProgress = self.config.get('r_amz_shops_inProgress')
  115. params = json.dumps({
  116. "type": self.business_type,
  117. "browserOauth": browserOauth,
  118. "browserName": browser['browserName'],
  119. "browserIp": browser['browserIp'],
  120. "siteId": browser['siteId'],
  121. "site_name": browser['site_name'],
  122. "isExpired": browser['isExpired']
  123. })
  124. # 检索该店铺数据是否已采集完成?
  125. is_sismember = self.obj_redis.sismember(key_completed, params)
  126. if is_sismember:
  127. logger.info('%s, 已采集完成.' % browserOauth)
  128. else:
  129. self.obj_redis.sadd(key_inProgress, params)
  130. pass
  131. else:
  132. # 代理IP过期告警...
  133. title = "Amazon·货件状态" # 悬浮标题
  134. iphone = self.config.get('ding_talk_iphone') # @的指定人
  135. spider_name = self.config.get('sn_v_shipment_status') # 应用名称
  136. browserName = browser['browserName'] # 店铺
  137. site_name = browser['site_name'] # 所属平台
  138. browserIp = browser['browserIp'] # 代理IP
  139. cloud_server = self.config.get('cloud_server_name') # 云服务器名称
  140. # 通知内容
  141. inform_content = "##### @{} Amazon·货件状态*>应用名称: {}*>店铺: {}*>所属平台: {}*>代理IP: {}*>" \
  142. "服务器: {}*>当前时间: {}*>店铺ID: {}*>是否过期:" \
  143. " <font color=#FFOOOO size=3 face='隶书'>代理IP已过期</font>*>" \
  144. .format(iphone, spider_name, browserName, site_name, browserIp,
  145. cloud_server, current_time, browserOauth).replace('*', '\n\n')
  146. self.utils.ding_talk_robot(1, title, inform_content, [iphone], False)
  147. self.utils.sleep_message(5, "间歇....")
  148. pass
  149. pass
  150. else:
  151. logger.warning("statusCode:%s, err: %s" % (shop_info['statusCode'], shop_info['err']))
  152. pass

(二)通过Selenium API 启动和控制超级浏览器内核

这个部分主要是由自动化程序开发者自行开发,需要自行了解Selenium API如何使用。启动Selenium时有些参数依赖与超级浏览器主进程通讯的结果

二、交互时序图

三、必要条件以及注意事项

(一)启动超级浏览器主进程必要配置启动参数

1、--run_type=web_driver

指定以Webdriver 模式运行主进程,本质是让超级浏览器以无界面状态运行。超级浏览器进程会自动保证进程唯一,所以多次启动会自动放弃后启动进程。但是手动点击启动的超级浏览器会Kill掉Webdriver 模式运行的进程

2、--socket_port=端口号

告诉超级浏览器双方socket通讯端口是什么,超级浏览器会以该端口在127.0.0.1启动一个socket服务端

  1. # 获取紫鸟·超级浏览器安装路径
  2. path_super_browser = self.config.get('path_super_browser')
  3. cmd = "{} --run_type=web_driver --socket_port={}".format(path_super_browser, self.port)
  4. subprocess.Popen(cmd)

(二)Socket通讯注意事项

1、每条Socket请求和响应数据必须以"\r\n"结尾

2、Socket请求内容和返回结果都是通过JSON结构组织,收发消息统一以UTF-8编码

3、Socket请求的相应都是异步返回的,可以并发执行,通过在请求参数里面添加一个全局唯一的requestId字段来标识请求,该字段会在响应内返回

  1. def socket_communication(self, params):
  2. """
  3. Socket通信
  4. :param params: 参数对象
  5. :return:
  6. """
  7. try:
  8. args = (str(params) + '\r\n').encode('utf-8')
  9. # 将 string 中的数据发送到连接的套接字
  10. self.tcpCliSock.send(args)
  11. # 接收的最大数据量
  12. res = self.tcpCliSock.recv(self.buf_size)
  13. return json.loads(res)
  14. except ConnectionResetError as e:
  15. logger.warning("ConnectionResetError: %s" % e)
  16. logger.info("socket 连接已关闭")
  17. except Exception as e:
  18. logger.error("socket_communication error: %s" % e)
  19. pass

(三)其他注意事项

1、getBrowserEnvInfo返回数据不能复用,每次都要重新调用。

2、需要根据运行设备的配置,适当控制同时启动的店铺浏览器窗口总个数,店铺刚刚启动时非常消耗CPU可以考虑错开启动。这个部分需要开发者自行调优,没有明确的标准。

四、Socket接口说明

(一)Action : getBrowserList

1、说明:获取店铺列表

2、请求参数:

  1. {
  2. "userInfo": "{\"company\":\"公司\",\"username\":\"用户名\",\"password\":\"密码\"}",
  3. "action": "getBrowserList",
  4. "requestId": "全局唯一标识"
  5. }

3、响应结果:

  1. {
  2. "statusCode": "状态码",
  3. "err": "异常信息",
  4. "action": "getBrowserList",
  5. "requestId": "全局唯一标识",
  6. "browserList": [{
  7. "browserOauth": "店铺ID",
  8. "browserName": "店铺名称",
  9. "browserIp": "店铺IP",
  10. "siteId": "店铺所属站点",
  11. "isExpired": false //ip是否过期
  12. }]
  13. }

4、状态码:

(1)0 : 成功
(2)-10000 : 未知异常
(3)-10002 : Socket参数非法
(4)-10003 : 登录失败
(5)-10004 : 获取店铺列表时服务器返回异常

  1. def browser_list(self):
  2. logger.info("")
  3. logger.info("获取店铺列表.")
  4. shop_list_params = self.browser_api(self.__GET_BROWSER_LIST)
  5. shop_info = self.socket_communication(shop_list_params)
  6. if shop_info['statusCode'] == 0:
  7. browser_size = len(shop_info['browserList'])
  8. logger.info("目前店铺总数: %s, 正在记录店铺信息...,请稍等." % browser_size)
  9. current_time = Utility.curr_time()
  10. for index, browser in enumerate(shop_info['browserList']):
  11. index += 1
  12. # site_id 对应的值
  13. browser['site_name'] = Mapping.SiteIdExplain(browser['siteId'])
  14. browserOauth = browser['browserOauth']
  15. if browser['isExpired'] is False:
  16. # 記錄店鋪的數據
  17. key_completed = self.config.get('r_amz_shops_completed')
  18. key_inProgress = self.config.get('r_amz_shops_inProgress')
  19. params = json.dumps({
  20. "type": self.business_type,
  21. "browserOauth": browserOauth,
  22. "browserName": browser['browserName'],
  23. "browserIp": browser['browserIp'],
  24. "siteId": browser['siteId'],
  25. "site_name": browser['site_name'],
  26. "isExpired": browser['isExpired']
  27. })
  28. # 檢索該店鋪數據是否已采集完成?
  29. is_sismember = self.obj_redis.sismember(key_completed, params)
  30. if is_sismember:
  31. logger.info('%s, 已采集完成.' % browserOauth)
  32. else:
  33. self.obj_redis.sadd(key_inProgress, params)
  34. pass
  35. else:
  36. # 代理IP过期告警...
  37. title = "Amazon·货件状态" # 悬浮标题
  38. iphone = self.config.get('ding_talk_iphone') # @的指定人
  39. spider_name = self.config.get('sn_v_shipment_status') # 应用名称
  40. browserName = browser['browserName'] # 店铺
  41. site_name = browser['site_name'] # 所属平台
  42. browserIp = browser['browserIp'] # 代理IP
  43. cloud_server = self.config.get('cloud_server_name') # 云服务器名称
  44. # 通知内容
  45. inform_content = "##### @{} Amazon·货件状态*>应用名称: {}*>店铺: {}*>所属平台: {}*>代理IP: {}*>" \
  46. "服务器: {}*>当前时间: {}*>店铺ID: {}*>是否过期:" \
  47. " <font color=#FFOOOO size=3 face='隶书'>代理IP已过期</font>*>" \
  48. .format(iphone, spider_name, browserName, site_name, browserIp,
  49. cloud_server, current_time, browserOauth).replace('*', '\n\n')
  50. self.utils.ding_talk_robot(1, title, inform_content, [iphone], False)
  51. self.utils.sleep_message(5, "间歇....")
  52. pass
  53. pass
  54. else:
  55. logger.warning("statusCode:%s, err: %s" % (shop_info['statusCode'], shop_info['err']))
  56. pass

(二)Action : startBrowser

1、说明:启动店铺,关闭店铺需要调用stopBrowser,连续两次调用startBrowser会视为重启

2、请求参数:

  1. {
  2. "userInfo": "{\"company\":\"公司\",\"username\":\"用户名\",\"password\":\"密码\"}",
  3. "action": "startBrowser",
  4. "browserOauth": "店铺ID",
  5. "isHeadless": true, //是否启用无头模式
  6. "requestId": "全局唯一标识"
  7. }

3、响应结果:

  1. {
  2. "statusCode": "状态码",
  3. "err": "异常信息",
  4. "action": "startBrowser",
  5. "browserOauth": "店铺ID",
  6. "requestId": "全局唯一标识",
  7. "launcherPage": "店铺所属平台的默认启动页面",
  8. "debuggingPort": "调试端口"
  9. }

4、启动Selenium 必要参数

//根据startBrowser返回结果启动Selenium
ChromeOptions options = new ChromeOptions();
//调试端口
options.setExperimentalOption("debuggerAddress", "127.0.0.1:" + debuggingPort);
//删除其他不需要参数

  1. def driver_browser(self, shop_obj):
  2. """
  3. Selenium驱动浏览器(Chrome)
  4. :param shop_obj: 店铺信息
  5. :return:
  6. """
  7. # 启动Selenium
  8. self.utils.sleep_message(3, "启动Selenium.")
  9. launcher_page, debugging_port = shop_obj['launcherPage'], shop_obj['debuggingPort']
  10. logger.info("启动Selenium必要参数: debugging_port: %s, launcher_page: %s" % (debugging_port, launcher_page))
  11. self.utils.sleep_message(2.5, "浏览器配置")
  12. options = webdriver.ChromeOptions()
  13. options.add_experimental_option("debuggerAddress", "127.0.0.1:" + str(debugging_port))
  14. driver = webdriver.Chrome(executable_path='./files/driver/windows/80.0.3987.163/chromedriver', options=options)
  15. self.utils.sleep_message(3, "进入店铺...")
  16. driver.get(launcher_page)
  17. return driver

5、状态码:

(1)0 : 成功
(2)-10000 : 未知异常
(3)-10001 : 内核窗口创建失败
(4)-10002 : Socket参数非法
(5)-10003 : 登录失败
(6)-10004 : browserOauth缺失
(7)-10005 : 该店铺上次请求的startBrowser还未执行结束
(8)大于零的状态码:
1 : 初始化数据失败
2 : 检测到当前IP无法正常使用,请联系客服
4 : 初始化时区失败
5 : 初始化代理失败
6 : 初始化黑白名单
7 : 启动内核失败
8 : 初始化浏览器个人目录
9 : 初始化Cookies失败
11 : 初始化浏览器设置文件
13 : 初始化代理信息配置

  1. def start_browser(self, shop_id):
  2. """
  3. 启动店铺
  4. :param shop_id: 店铺ID
  5. :return:
  6. """
  7. # 启动店铺(两种方式) startBrowser / getBrowserEnvInfo
  8. start_params = self.browser_api(self.__START_BROWSER, {"browserOauth": shop_id, "isHeadless": self.IS_HEADLESS})
  9. shop_obj = self.socket_communication(start_params)
  10. logger.info("启动店铺信息: %s" % shop_obj)
  11. return shop_obj

(三)Action : stopBrowser

1、说明:关闭店铺窗口

2、请求参数:

  1. {
  2. "userInfo": "{\"company\":\"公司\",\"username\":\"用户名\",\"password\":\"密码\"}",
  3. "action": "stopBrowser",
  4. "requestId": "全局唯一标识"
  5. }

3、响应结果:

  1. {
  2. "statusCode": "状态码",
  3. "err": "异常信息",
  4. "action": "stopBrowser",
  5. "requestId": "全局唯一标识"
  6. }

4、状态码:

(1) 0 : 成功
(2)-10000 : 未知异常
(3)-10002 : Socket参数非法
(4)-10003 : 登录失败

  1. def stop_browser(self, shop_id):
  2. """
  3. 关闭店铺
  4. :param shop_id: 店铺ID
  5. :return:
  6. """
  7. logger.info("关闭店铺")
  8. stop_params = self.browser_api(
  9. self.__STOP_BROWSER, {
  10. "browserOauth": shop_id,
  11. "isHeadless": self.IS_HEADLESS
  12. }
  13. )
  14. stop_obj = self.socket_communication(stop_params)
  15. logger.info("关闭店铺信息: %s" % stop_obj)

(四)Action : getBrowserEnvInfo

1、说明:和startBrowser类似,区别就是内核窗口startBrowser由主程序启动,getBrowserEnvInfo由Webdriver启动

2、请求参数

  1. {
  2. "userInfo": "{\"company\":\"公司\",\"username\":\"用户名\",\"password\":\"密码\"}",
  3. "action": "getBrowserEnvInfo",
  4. "browserOauth": "店铺ID",
  5. "isHeadless": true, //是否启用无头模式
  6. "requestId": "全局唯一标识"
  7. }

3、响应结果:

  1. {
  2. "statusCode": "状态码",
  3. "err": "异常信息",
  4. "action": "getBrowserEnvInfo",
  5. "browserOauth": "店铺ID",
  6. "requestId": "全局唯一标识",
  7. "browserPath": "内核exe所在位置",
  8. "launcherPage": "店铺所属平台的默认启动页面",
  9. "browserArguments": "启动必要参数",
  10. "debuggingPort": "调试端口"
  11. }

4、启动Selenium 必要参数

//根据getBrowserEnvInfo 返回结果启动Selenium
ChromeOptions options = new ChromeOptions();
//内核exe所在位置
options.addArguments(browserArguments);
//启动必要参数
options.setBinary(browserPath);
//调试端口
options.addArguments("--remote-debugging-port=" + debuggingPort);

5、状态码:参考Action : startBrowser

(五)Action : heartbeat

1、说明:非必要接口,只是用于保活Socket连接

2、请求参数

  1. {
  2. "userInfo": "{\"company\":\"公司\",\"username\":\"用户名\",\"password\":\"密码\"}",
  3. "action": "heartbeat",
  4. "requestId": "全局唯一标识"
  5. }

3、响应结果:

  1. {
  2. "statusCode": "状态码",
  3. "err": "异常信息",
  4. "action": "heartbeat",
  5. "requestId": "全局唯一标识"
  6. }

4、状态码:

(1) 0 : 成功
(2)-10000 : 未知异常
(3)-10002 : Socket参数非法
(4)-10003 : 登录失败

  1. def heartbeat(self):
  2. """维持心跳"""
  3. self.utils.sleep_message(10, "维持心跳")
  4. heartbeat_params = self.browser_api(self.__HEARTBEAT)
  5. heartbeat_obj = self.socket_communication(heartbeat_params)
  6. logger.info("心跳信息: %s" % heartbeat_obj)

(六)Action : exit

1、说明:正常退出超级浏览器主进程,会自动关闭已启动python教程店铺并保持店铺cookie等信息。

2、请求参数:

  1. {
  2. "userInfo": "{\"company\":\"公司\",\"username\":\"用户名\",\"password\":\"密码\"}",
  3. "action": "exit",
  4. "requestId": "全局唯一标识"
  5. }

3、响应码:

4、状态码:

(1) 0 : 成功
(2)-10000 : 未知异常
(3)-10002 : Socket参数非法
(4)-10003 : 登陆失败

  1. def exit_browser(self):
  2. self.utils.sleep_message(10, "退出浏览器 .....")
  3. # 退出浏览器
  4. exit_params = self.browser_api(self.__EXIT)
  5. logger.info("退出浏览器: %s" % exit_params)
  6. self.socket_communication(exit_params)
  7. # 杀死浏览器进程
  8. # self.kill_browser()
  9. # ------------------------>> 钉钉通知公共参数 start
  10. title = "紫鸟·超级浏览器·退出" # 悬浮标题
  11. iphone = self.config.get('ding_talk_iphone') # @的指定人
  12. spider_name = self.config.get('sn_v_shipment_status') # 应用名称
  13. curr_time = Utility.curr_time() # 当前时间
  14. cloud_server = self.config.get('cloud_server_name') # 云服务器名称
  15. # ------------------------>> 钉钉通知公共参数 end
  16. inform_content = "##### 紫鸟·超级浏览器·退出*>应用名称: {}*> 服务器: {}*>当前时间: {}*>" \
  17. "<font color=#00DD00 size=3 face='隶书'>采集任务已完成,浏览器正常退出.</font>*>" \
  18. .format(spider_name, cloud_server, curr_time).replace('*', '\n\n')
  19. Utility.ding_talk_robot(1, title, inform_content, [iphone], False)

五、getBrowserList中的SiteId说明

indexidname
11?? 美国亚马逊
22?? 加拿大亚马逊
33?? 日本亚马逊
44?? 英国亚马逊
55?? 法国亚马逊
67?? 意大利亚马逊
710?? 德国亚马逊
811?? 西班牙亚马逊

写得有问题的地方还望各位大佬指出错误,谢谢。

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载