yaoyigou_crawl.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import time
  2. import requests
  3. import json
  4. from commons.Logger import get_spider_logger
  5. from pipelines.drug_pipelines import DrugPipeline
  6. logger = get_spider_logger("yaoyigou")
  7. user_name = '青羊新洋诊所'
  8. password = '123321'
  9. headers = {
  10. 'accept': 'application/json, text/plain, */*',
  11. 'accept-language': 'zh-CN,zh;q=0.9',
  12. 'content-type': 'application/json;charset=UTF-8',
  13. 'origin': 'https://www.hezongyy.com',
  14. 'priority': 'u=1, i',
  15. 'referer': 'https://www.hezongyy.com/',
  16. 'sec-ch-ua': '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"',
  17. 'sec-ch-ua-mobile': '?0',
  18. 'sec-ch-ua-platform': '"Windows"',
  19. 'sec-fetch-dest': 'empty',
  20. 'sec-fetch-mode': 'cors',
  21. 'sec-fetch-site': 'same-site',
  22. 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36',
  23. }
  24. class Yaoyigou:
  25. def __init__(self, task_dict=None,scheduler = None):
  26. self.hesytoken = None
  27. self.headers = headers.copy()
  28. self.platform = 8
  29. self.collect_task_id = None
  30. self.pipeline = DrugPipeline("yaoyigou")
  31. self.company_id = 0
  32. self.brand = ""
  33. self.is_success = True
  34. self.task_dict = task_dict
  35. self.scheduler =scheduler
  36. self.number = number
  37. if self.task_dict:
  38. self.get_product_data()
  39. def _error_report(self,data):
  40. self.report_data.update(data)
  41. self.scheduler.stop()
  42. self.scheduler.post_report(self.report_data)
  43. def _success_report(self,data):
  44. self.report_data.update(data)
  45. self.scheduler.post_report(self.report_data)
  46. def get_product_data(self):
  47. self.task_id = self.task_dict["id"]
  48. self.company_id = self.task_dict["company_id"]
  49. self.product = self.task_dict["product_name"]
  50. self.product_desc = self.task_dict.get("product_specs", "")
  51. self.brand = self.task_dict.get("product_brand", "")
  52. self.product_keyword = self.task_dict.get("product_keyword", "")
  53. self.collect_task_id = self.task_dict.get("collect_task_id", "")
  54. self.sampling_cycle = self.task_dict.get("sampling_cycle", "")
  55. self.sampling_start_time = self.task_dict.get("sampling_start_time", "")
  56. self.sampling_end_time = self.task_dict.get("sampling_end_time", "")
  57. self.collect_equipment_id = self.task_dict.get("collect_equipment_id", "")
  58. self.account_id = self.task_dict.get("collect_equipment_account_id", "")
  59. self.collect_region_id = self.task_dict.get("collect_region_id", "")
  60. self.collect_round = self.task_dict.get("collect_round",1)
  61. self.report_data = {'task_id':self.task_id,'platform':self.platform,'username':self.scheduler.username,'total_pages':1}
  62. def _post_json_with_retry(self, url, payload, max_retry=1, timeout=20):
  63. """
  64. 请求失败后重试一次(默认总尝试 2 次)。
  65. """
  66. for attempt in range(max_retry + 1):
  67. try:
  68. response = requests.post(
  69. url,
  70. headers=self.headers,
  71. json=payload,
  72. timeout=timeout,
  73. )
  74. response.raise_for_status()
  75. return response.json()
  76. except Exception as e:
  77. if attempt < max_retry:
  78. logger.warning("请求失败,准备重试(%s/%s): %s", attempt + 1, max_retry + 1, e)
  79. time.sleep(2)
  80. continue
  81. logger.error("请求失败,已达最大重试次数: %s", e)
  82. raise
  83. def login_yaoyigou(self):
  84. json_data = {
  85. 'username': user_name,
  86. 'password': password,
  87. 'channel': 1,
  88. 'timeout': 604800,
  89. 'fingerprint': 'b86bae775df98bc20c5199dab0ac4edb',
  90. 'companyLimited': 0, }
  91. try:
  92. data_json = self._post_json_with_retry(
  93. 'https://newapi.hezongyy.com/users/UserLogin/login',
  94. json_data,
  95. max_retry=1,
  96. timeout=20,
  97. )
  98. print(data_json)
  99. self.hesytoken = data_json.get('content', "")
  100. except Exception as e:
  101. logger.error("登录失败: %s", e)
  102. self.hesytoken = ""
  103. return self.hesytoken
  104. def parse_product(self, item):
  105. item_id = item.get("id")
  106. now = time.strftime("%Y-%m-%d %H:%M:%S")
  107. safe_item_id = str(item_id).strip() if item_id not in (None, "") else ""
  108. detail_url = ""
  109. if safe_item_id:
  110. detail_url = f"https://www.hezongyy.com/#/goodsDetails?id={safe_item_id}"
  111. # 字段与 yaofangwang_crawl 中 product 对齐(供 DrugPipeline)
  112. product = {
  113. "platform": self.platform,
  114. "item_id": item_id,
  115. "enterprise_id": self.company_id,
  116. "product_name": item.get("name", ""),
  117. "spec": item.get("specification", ""),
  118. "one_price": "",
  119. "detail_url": detail_url,
  120. "shop_name": "",
  121. "anonymous_store_name": "",
  122. "shop_url": "",
  123. "city_name": "",
  124. "city_id": "",
  125. "province_name": "",
  126. "province_id": "",
  127. "shipment_city_name": "",
  128. "shipment_city_id": "",
  129. "shipment_province_name": "",
  130. "shipment_province_id": "",
  131. "area_info": "",
  132. "factory_name": item.get("manufacturerName", ""),
  133. "scrape_date": time.strftime("%Y-%m-%d"),
  134. "price": item.get("sellingPrice"),
  135. "sales": "",
  136. "stock_count": item.get("quantity", ""),
  137. "snapshot_url": "",
  138. "approval_num": item.get("licenseNumber"),
  139. "produced_time": "",
  140. "deadline": item.get("expirationDate", ""),
  141. "update_time": now,
  142. "insert_time": now,
  143. "number": 1,
  144. "product_brand": self.brand or "",
  145. "collect_task_id": self.collect_task_id,
  146. "search_name": self.product,
  147. "company_name": "",
  148. "collect_config_info": json.dumps(
  149. {
  150. "sampling_cycle": self.sampling_cycle,
  151. "sampling_start_time": self.sampling_start_time,
  152. "sampling_end_time": self.sampling_end_time,
  153. }
  154. ),
  155. "account_id": self.account_id,
  156. "collect_region_id": self.collect_region_id,
  157. "collect_round": self.collect_round,
  158. "is_sold_out": 1
  159. }
  160. return product
  161. def get_good_ids(self, page_num):
  162. keyword = self.product_keyword or self.product
  163. json_data = {
  164. 'channelType': 0,
  165. 'columnType': 0,
  166. 'searchType': 0,
  167. 'pageNumber': page_num,
  168. 'pageSize': 20,
  169. 'keyWords': keyword,
  170. 'newCategoryLabelId': '-1',
  171. }
  172. good_ids = []
  173. data_json = self._post_json_with_retry(
  174. 'https://newapi.hezongyy.com/goods/search/onCondition',
  175. json_data,
  176. max_retry=1,
  177. timeout=20,
  178. )
  179. list_data = data_json.get("content", {}).get("list", [])
  180. for list_item in list_data:
  181. good_ids.append(str(list_item.get("goodsId")) + '0')
  182. return good_ids
  183. def search_data(self):
  184. for page_num in range(1, 20):
  185. good_ids = self.get_good_ids(page_num)
  186. if not good_ids:
  187. break
  188. json_data = {
  189. 'goodsIdList': good_ids,
  190. 'limitSize': 20,
  191. 'limitStart': (page_num - 1) * 20,
  192. 'keyword': self.product_keyword or self.product,
  193. 'clientType': 1,
  194. }
  195. json_data = self._post_json_with_retry(
  196. 'https://newapi.hezongyy.com/goods/goods/listNormal',
  197. json_data,
  198. max_retry=1,
  199. timeout=20,
  200. )
  201. contents = json_data.get("content", [])
  202. if not contents:
  203. break
  204. for content in contents:
  205. company = content.get("manufacturerName", "")
  206. # if self.brand not in company:
  207. # continue
  208. try:
  209. product = self.parse_product(content)
  210. if not product.get("item_id"):
  211. continue
  212. self.pipeline.storge_data(product)
  213. logger.info(f"入库成功: {json.dumps(product, ensure_ascii=False)}")
  214. except Exception as e:
  215. logger.exception("入库失败: %s", e)
  216. def run(self):
  217. self.login_yaoyigou()
  218. if not self.hesytoken:
  219. logger.error(f"请检查账号是否出现问题")
  220. self.is_success = False
  221. return self.pipeline.crawl_count, self.is_success
  222. self.headers["hesytoken"] = self.hesytoken
  223. try:
  224. self.search_data()
  225. self._success_report({'is_finished':1,'need_reassign':0,'current_page':1})
  226. except Exception as e:
  227. logger.error(f"爬取搜索失败{str(e)}")
  228. self._error_report({'is_finished':0,'need_reassign':1,'current_page':1,'exception_type':5})
  229. self.is_success = False
  230. return self.pipeline.crawl_count, self.is_success