亚马逊网站用户的评论能直观的反映当前商品值不值得付费,评分信息也能获取到做一个评分的权重。
亚马逊的评论区由用户ID,评分及评论标题,地区时间,评论正文 这几个部分组成,本次获取的内容就是这些。
测试链接:
https://www.amazon.it/product-reviews/B08GHGTGQ2/ref=cm_cr_arp_d_paging_btm_14?ie=UTF8&pageNumber=14&reviewerType=all_reviews&pageSize=10&sortBy=recent
一、分析亚马逊的评论请求
首先打开开发者模式的Network,Clear清屏做一次请求:
你会发现在Doc中的get请求正好就有我们想要的评论信息。
可是真正的评论数据可不是全部都在这里的,页面往下翻,有个翻页的button:
点击翻页请求下一页,在Fetch/XHR选项卡中多了一个新的请求,刚才的Doc选项卡中并无新的get请求。这下发现了所有的评论信息是XHR类型的请求。
获取到post请求的链接和payload数据,里面含有控制翻页的参数,真正的评论请求已经找到了。
这一堆就是未处理的信息,这些请求未处理的信息里面,带有data-hook=\"review\"的就是带有评论的信息。分析完毕,下面开始一步一步去写请求。
二、获取亚马逊评论的内容
首先拼凑请求所需的post参数,请求链接,以便之后的自动翻页,然后带参数post请求链接:
1. headers = { 2. 'authority': 'www.amazon.it', 3. "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 4. "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36", 5. } 6. 7. page = 1 8. post_data = { 9. "sortBy": "recent", 10. "reviewerType": "all_reviews", 11. "formatType": "", 12. "mediaType": "", 13. "filterByStar": "", 14. "filterByLanguage": "", 15. "filterByKeyword": "", 16. "shouldAppend": "undefined", 17. "deviceType": "desktop", 18. "canShowIntHeader": "undefined", 19. "pageSize": "10", 20. "asin": "B08GHGTGQ2", 21. } 22. # 翻页关键payload参数赋值 23. post_data["pageNumber"] = page, 24. post_data["reftag"] = f"cm_cr_getr_d_paging_btm_next_{page}", 25. post_data["scope"] = f"reviewsAjax{page}", 26. # 翻页链接赋值 27. spiderurl=f'https://www.amazon.it/hz/reviewsrender/ajax/reviews/get/ref=cm_cr_getr_d_paging_btm_next_{page}' 28. res = requests.post(spiderurl,headers=headers,data=post_data) 29. if res and res.status_code == 200: 30. res = res.content.decode('utf-8') 31. print(res)
现在已经获取到了这一堆未处理的信息,接下来开始对这些数据进行处理。
三、亚马逊评论信息的处理
上图的信息会发现,每一段的信息都由“&&&”进行分隔,而分隔之后的每一条信息都是由'","'分隔开的:
所以用python的split方法进行处理,把字符串分隔成list列表:
1. # 返回值字符串处理 2. contents = res.split('&&&') 3. for content in contents: 4. infos = content.split('","')
由'","'分隔的数据通过split处理生成新的list列表,评论内容是列表的最后一个元素,去掉里面的"\","\n"和多余的符号,就可以通过css/xpath选择其进行处理了:
1. for content in contents: 2. infos = content.split('","') 3. info = infos[-1].replace('"]','').replace('\\n','').replace('\\','') 4. # 评论内容判断 5. if 'data-hook="review"' in info: 6. sel = Selector(text=info) 7. data = {} 8. data['username'] = sel.xpath('//span[@class="a-profile-name"]/text()').extract_first() #用户名 9. data['point'] = sel.xpath('//span[@class="a-icon-alt"]/text()').extract_first() #评分 10. data['date'] = sel.xpath('//span[@data-hook="review-date"]/text()').extract_first() #日期地址 11. data['review'] = sel.xpath('//span[@data-hook="review-title"]/span/text()').extract_first() #评价标题 12. data['detail'] = sel.xpath('//span[@data-hook="review-body"]').extract_first() #评价内容 13. image = sel.xpath('div[@class="review-image-tile-section"]').extract_first() 14. data['image'] = image if image else "not image" #图片 15. print(data)
四、代码整合
4.1 代理设置
稳定的IP代理是你数据获取最有力的工具。目前国内还是无法稳定的访问亚马逊,会出现连接失败的情况。我这里使用的ipidea代理请求的意大利地区的亚马逊,可以通过账密和api获取代理,速度还是非常稳定的。
下面的代理获取的方法:
1. # api获取ip 2. def getApiIp(self): 3. # 获取且仅获取一个ip------意大利 4. api_url = '获取代理地址' 5. res = requests.get(api_url, timeout=5) 6. try: 7. if res.status_code == 200: 8. api_data = res.json()['data'][0] 9. proxies = { 10. 'http': 'http://{}:{}'.format(api_data['ip'], api_data['port']), 11. 'https': 'http://{}:{}'.format(api_data['ip'], api_data['port']), 12. } 13. print(proxies) 14. return proxies 15. else: 16. print('获取失败') 17. except: 18. print('获取失败')
4.2 while循环翻页
while循环进行翻页,评论最大页数是99页,99页之后就break跳出while循环:
1. def getPLPage(self): 2. while True: 3. # 翻页关键payload参数赋值 4. self.post_data["pageNumber"]= self.page, 5. self.post_data["reftag"] = f"cm_cr_getr_d_paging_btm_next_{self.page}", 6. self.post_data["scope"] = f"reviewsAjax{self.page}", 7. # 翻页链接赋值 8. spiderurl = f'https://www.amazon.it/hz/reviews-render/ajax/reviews/get/ref=cm_cr_getr_d_paging_btm_next_{self.page}' 9. res = self.getRes(spiderurl,self.headers,'',self.post_data,'POST',check)#自己封装的请求方法 10. if res: 11. res = res.content.decode('utf-8') 12. # 返回值字符串处理 13. contents = res.split('&&&') 14. for content in contents: 15. infos = content.split('","') 16. info = infos[-1].replace('"]','').replace('\\n','').replace('\\','') 17. # 评论内容判断 18. if 'data-hook="review"' in info: 19. sel = Selector(text=info) 20. data = {} 21. data['username'] = sel.xpath('//span[@class="a-profile-name"]/text()').extract_first() #用户名 22. data['point'] = sel.xpath('//span[@class="a-icon-alt"]/text()').extract_first() #评分 23. data['date'] = sel.xpath('//span[@data-hook="review-date"]/text()').extract_first() #日期地址 24. data['review'] = sel.xpath('//span[@data-hook="review-title"]/span/text()').extract_first() #评价标题 25. data['detail'] = sel.xpath('//span[@data-hook="review-body"]').extract_first() #评价内容 26. image = sel.xpath('div[@class="review-image-tile-section"]').extract_first() 27. data['image'] = image if image else "not image" #图片 28. print(data) 29. if self.page <= 99: 30. print('Next Page') 31. self.page += 1 32. else: 33. break
最后的整合代码:
1. # coding=utf-8 2. import requests 3. from scrapy import Selector 4. 5. class getReview(): 6. page = 1 7. headers = { 8. 'authority': 'www.amazon.it', 9. "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", 10. "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36", 11. } 12. post_data = { 13. "sortBy": "recent", 14. "reviewerType": "all_reviews", 15. "formatType": "", 16. "mediaType": "", 17. "filterByStar": "", 18. "filterByLanguage": "", 19. "filterByKeyword": "", 20. "shouldAppend": "undefined", 21. "deviceType": "desktop", 22. "canShowIntHeader": "undefined", 23. "pageSize": "10", 24. "asin": "B08GHGTGQ2", 25. } 26. #post_data中asin参数目前写死在 27. #"https://www.amazon.it/product-reviews/B08GHGTGQ2?ie=UTF8&pageNumber=1&reviewerType=all_reviews&pageSize=10&sortBy=recent" 28. #这个链接里,不排除asin值变化的可能,如要获取get请求即可 29. 30. def getPLPage(self): 31. while True: 32. # 翻页关键payload参数赋值 33. self.post_data["pageNumber"]= self.page, 34. self.post_data["reftag"] = f"cm_cr_getr_d_paging_btm_next_{self.page}", 35. self.post_data["scope"] = f"reviewsAjax{self.page}", 36. # 翻页链接赋值 37. spiderurl = f'https://www.amazon.it/hz/reviews-render/ajax/reviews/get/ref=cm_cr_getr_d_paging_btm_next_{self.page}' 38. res = self.getRes(spiderurl,self.headers,'',self.post_data,'POST',check)#自己封装的请求方法 39. if res: 40. res = res.content.decode('utf-8') 41. # 返回值字符串处理 42. contents = res.split('&&&') 43. for content in contents: 44. infos = content.split('","') 45. info = infos[-1].replace('"]','').replace('\\n','').replace('\\','') 46. # 评论内容判断 47. if 'data-hook="review"' in info: 48. sel = Selector(text=info) 49. data = {} 50. data['username'] = sel.xpath('//span[@class="a-profile-name"]/text()').extract_first() #用户名 51. data['point'] = sel.xpath('//span[@class="a-icon-alt"]/text()').extract_first() #评分 52. data['date'] = sel.xpath('//span[@data-hook="review-date"]/text()').extract_first() #日期地址 53. data['review'] = sel.xpath('//span[@data-hook="review-title"]/span/text()').extract_first() #评价标题 54. data['detail'] = sel.xpath('//span[@data-hook="review-body"]').extract_first() #评价内容 55. image = sel.xpath('div[@class="review-image-tile-section"]').extract_first() 56. data['image'] = image if image else "not image" #图片 57. print(data) 58. if self.page <= 99: 59. print('Next Page') 60. self.page += 1 61. else: 62. break 63. 64. # api获取ip 65. def getApiIp(self): 66. # 获取且仅获取一个ip------意大利 67. api_url = '获取代理地址' 68. res = requests.get(api_url, timeout=5) 69. try: 70. if res.status_code == 200: 71. api_data = res.json()['data'][0] 72. proxies = { 73. 'http': 'http://{}:{}'.format(api_data['ip'], api_data['port']), 74. 'https': 'http://{}:{}'.format(api_data['ip'], api_data['port']), 75. } 76. print(proxies) 77. return proxies 78. else: 79. print('获取失败') 80. except: 81. print('获取失败') 82. 83. #专门发送请求的方法,代理请求三次,三次失败返回错误 84. def getRes(self,url,headers,proxies,post_data,method): 85. if proxies: 86. for i in range(3): 87. try: 88. # 传代理的post请求 89. if method == 'POST': 90. res = requests.post(url,headers=headers,data=post_data,proxies=proxies) 91. # 传代理的get请求 92. else: 93. res = requests.get(url, headers=headers,proxies=proxies) 94. if res: 95. return res 96. except: 97. print(f'第{i+1}次请求出错') 98. else: 99. return None 100. else: 101. for i in range(3): 102. proxies = self.getApiIp() 103. try: 104. # 请求代理的post请求 105. if method == 'POST': 106. res = requests.post(url, headers=headers, data=post_data, proxies=proxies) 107. # 请求代理的get请求 108. else: 109. res = requests.get(url, headers=headers, proxies=proxies) 110. if res: 111. return res 112. except: 113. print(f"第{i+1}次请求出错") 114. else: 115. return None 116. 117. if __name__ == '__main__': 118. getReview().getPLPage()
总结
本次的亚马逊评论获取就是两个坑,一是评论信息通过的XHR请求方式,二是评论信息的处理。分析之后这次的数据获取还是非常简单的,找到正确的请求方式,稳定的IP代理让你事半功倍,找到信息的共同点进行处理,问题就迎刃而解了。