zoukankan      html  css  js  c++  java
  • Python爬虫-按给定关键词-爬取京东商品信息

    目的:按给定关键词爬取京东商品信息,并保存至mongodb。

    字段:title、url、store、store_url、item_id、price、comments_count、comments

    工具:requests、lxml、pymongo、concurrent

    分析:

    1.  https://search.jd.com/Search?keyword=耳机&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=er%27ji&page=1&s=56&click=0,这是京东搜索耳机的跳转url,其中关键参数为:

     keyword:关键词

     enc:字符串编码

     page:页码,需要注意的是,这里的数值均为奇数

     所以简化后的 url 为 https://search.jd.com/Search?keyword=耳机&enc=utf-8&page=1

    2.  分析各字段的 xpath,发现在搜索页面只能匹配到 title、url、store、store_url、item_id、price。至于 comments_count、comments 需要单独发出请求。

    3.  打开某一商品详情页,点击商品评价,打开开发者工具。点击评论区的下一页,发现在新的请求中,除去响应为媒体格式外,仅多出一个 js 响应,故猜测评论内容包含其中。

    4.  分析上述请求的 url,简化后为 https://sclub.jd.com/comment/productPageComments.action?productId=100004325476&score=0&sortType=5&page=0&pageSize=10,其中:

     productId:商品的Id,可简单的从详情页的 url 中获取

     page:评论页码

    5.  由以上可以得出,我们需要先从搜索页面中获取的商品 id,通过 id 信息再去获取评论信息。爬取评论时需要注意,服务器会判断请求头中的 Referer,即只有通过商品详情页访问才能得到评论,所以我们每次都根据 item_id 构造请求头。

    6.  先将基础信息插入至数据库,在得到评论信息后,根据索引 item_id 将其补充完整。

    代码:

      1 import requests
      2 from lxml import etree
      3 import pymongo
      4 from concurrent import futures
      5 
      6 
      7 class CrawlDog:
      8     def __init__(self, keyword):
      9         """
     10         初始化
     11         :param keyword: 搜索的关键词
     12         """
     13         self.keyword = keyword
     14         self.mongo_client = pymongo.MongoClient(host='localhost')
     15         self.mongo_collection = self.mongo_client['spiders']['jd']
     16         self.mongo_collection.create_index([('item_id', pymongo.ASCENDING)])
     17 
     18     def get_index(self, page):
     19         """
     20         从搜索页获取相应信息并存入数据库
     21         :param page: 搜索页的页码
     22         :return: 商品的id
     23         """
     24         url = 'https://search.jd.com/Search?keyword=%s&enc=utf-8&page=%d' % (self.keyword, page)
     25         index_headers = {
     26             'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,'
     27                       'application/signed-exchange;v=b3',
     28             'accept-encoding': 'gzip, deflate, br',
     29             'Accept-Charset': 'utf-8',
     30             'accept-language': 'zh,en-US;q=0.9,en;q=0.8,zh-TW;q=0.7,zh-CN;q=0.6',
     31             'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
     32                           'Chrome/74.0.3729.169 Safari/537.36'
     33         }
     34         rsp = requests.get(url=url, headers=index_headers).content.decode()
     35         rsp = etree.HTML(rsp)
     36         items = rsp.xpath('//li[contains(@class, "gl-item")]')
     37         for item in items:
     38             try:
     39                 info = dict()
     40                 info['title'] = ''.join(item.xpath('.//div[@class="p-name p-name-type-2"]//em//text()'))
     41                 info['url'] = 'https:' + item.xpath('.//div[@class="p-name p-name-type-2"]/a/@href')[0]
     42                 info['store'] = item.xpath('.//div[@class="p-shop"]/span/a/text()')[0]
     43                 info['store_url'] = 'https' + item.xpath('.//div[@class="p-shop"]/span/a/@href')[0]
     44                 info['item_id'] = info.get('url').split('/')[-1][:-5]
     45                 info['price'] = item.xpath('.//div[@class="p-price"]//i/text()')[0]
     46                 info['comments'] = []
     47                 self.mongo_collection.insert_one(info)
     48                 yield info['item_id']
     49             # 实际爬取过程中有一些广告, 其中的一些上述字段为空
     50             except IndexError:
     51                 print('item信息不全, drop!')
     52                 continue
     53 
     54     def get_comment(self, params):
     55         """
     56         获取对应商品id的评论
     57         :param params: 字典形式, 其中item_id为商品id, page为评论页码
     58         :return:
     59         """
     60         url = 'https://sclub.jd.com/comment/productPageComments.action?productId=%s&score=0&sortType=5&page=%d&' 
     61               'pageSize=10' % (params['item_id'], params['page'])
     62         comment_headers = {
     63             'Referer': 'https://item.jd.com/%s.html' % params['item_id'],
     64             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
     65                           'Chrome/74.0.3729.169 Safari/537.36'
     66         }
     67         rsp = requests.get(url=url, headers=comment_headers).json()
     68         comments_count = rsp.get('productCommentSummary').get('commentCountStr')
     69         comments = rsp.get('comments')
     70         comments = [comment.get('content') for comment in comments]
     71         self.mongo_collection.update_one(
     72             # 定位至相应数据
     73             {'item_id': params['item_id']},
     74             {
     75                 '$set': {'comments_count': comments_count},  # 添加comments_count字段
     76                 '$addToSet': {'comments': {'$each': comments}}  # 将comments中的每一项添加至comments字段中
     77             }, True)
     78 
     79     def main(self, index_pn, comment_pn):
     80         """
     81         实现爬取的函数
     82         :param index_pn: 爬取搜索页的页码总数
     83         :param comment_pn: 爬取评论页的页码总数
     84         :return:
     85         """
     86         # 爬取搜索页函数的参数列表
     87         il = [i * 2 + 1 for i in range(index_pn)]
     88         # 创建一定数量的线程执行爬取
     89         with futures.ThreadPoolExecutor(15) as executor:
     90             res = executor.map(self.get_index, il)
     91         for item_ids in res:
     92             # 爬取评论页函数的参数列表
     93             cl = [{'item_id': item_id, 'page': page} for item_id in item_ids for page in range(comment_pn)]
     94             with futures.ThreadPoolExecutor(15) as executor:
     95                 executor.map(self.get_comment, cl)
     96 
     97 
     98 if __name__ == '__main__':
     99     # 测试, 只爬取两页搜索页与两页评论
    100     test = CrawlDog('耳机')
    101     test.main(2, 2)

    总结:爬取的过程中可能会被封 IP,测试时评论页面的获取被封锁,使用代理可以解决该问题,后面会来主要说一下代理的使用。

  • 相关阅读:
    浅析PostgreSQL的 ON CONFLICT 和 upsert:不存在则插入/存在则更新、upsert 介绍、语法及示例
    常见工作场景解决方案开源库推荐:文件上传库
    [转]Go-micro 服务端、客户端简单示例
    micro 与go-micro的区别
    【转】一篇文章说清楚 TDengine 的 FQDN
    【转】LV扩容(lvextend)
    [AWS] Launch configuration vs Launch template
    AcWing 867. 分解质因数
    AcWing 866. 试除法判定质数
    AcWing 861. 二分图的最大匹配
  • 原文地址:https://www.cnblogs.com/yangshaolun/p/10920761.html
Copyright © 2011-2022 走看看