rhat398

Untitled

Jan 11th, 2023
930
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.97 KB | None | 0 0
  1. import scrapy
  2. from urllib.parse import urlencode
  3. import re
  4. import json
  5. import html
  6. import math
  7.  
  8.  
  9. class SaksFifthAvenueSpider(scrapy.Spider):
  10.     name = "safa-feeds"
  11.  
  12.     # custom settings
  13.     custom_settings = {
  14.         "LOG_FILE": "safa-feeds-spider.log",
  15.         "ITEM_PIPELINES": {
  16.             "sdt.pipelines.SdtWasabiS3Pipeline": 300,
  17.         },
  18.     }
  19.  
  20.     headers = {
  21.         "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0",
  22.         "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  23.         "Accept-Language": "en-US,en;q=0.5",
  24.         "Connection": "keep-alive",
  25.         "Upgrade-Insecure-Requests": "1",
  26.         "Sec-Fetch-Dest": "document",
  27.         "Sec-Fetch-Mode": "navigate",
  28.         "Sec-Fetch-Site": "cross-site",
  29.         "Sec-GPC": "1",
  30.     }
  31.  
  32.     params = {
  33.         "cgid": "",
  34.         "start": "68",
  35.         "sz": "24",
  36.         "hideLess": "true",
  37.     }
  38.  
  39.     base_url = "https://www.saksfifthavenue.com/on/demandware.store/Sites-SaksFifthAvenue-Site/en_US/Search-UpdateGrid?"
  40.  
  41.     def start_requests(self):
  42.         cgurl_list = [
  43.             "https://www.saksfifthavenue.com/c/women-s-apparel",
  44.             # "2534374306624247",
  45.             # "2534374306622828",
  46.             # "1608313652004",
  47.             # "2534374306418050",
  48.             # "2534374306418162",
  49.             # "2534374306418054",
  50.             # "1654108780232",
  51.             # "2534374306418205",
  52.             # "2534374306418206",
  53.             # "2534374306418217",
  54.             # "2534374306418192",
  55.             # "1608313652004",
  56.             # "2534374306418053",
  57.         ]
  58.         for i, cgurl in enumerate(cgurl_list):
  59.             yield scrapy.Request(
  60.                 url=cgurl, headers=self.headers, callback=self.parse_page_numbers
  61.             )
  62.  
  63.     def parse_page_numbers(self, response):
  64.         total_items = int(response.css("span::attr(data-search-count)").get())
  65.         total_pages = round(math.ceil(total_items) / 21)
  66.         for i in range(0, int(total_pages)):
  67.             page_no = i * 21
  68.             url = response.url + f"?start={page_no}&sz=24"
  69.             yield scrapy.Request(
  70.                 url=url,
  71.                 headers=self.headers,
  72.                 callback=self.parse_page_items,
  73.             )
  74.  
  75.     def parse_page_items(self, response):
  76.         item_links = [
  77.             "https://www.saksfifthavenue.com" + i
  78.             for i in response.css("h3.pdp-link ::attr(href)").extract()
  79.         ]
  80.         # item_links = set(
  81.         #     [
  82.         #         "https://www.saksfifthavenue.com" + u.split("?")[0]
  83.         #         for u in response.css("a.thumb-link.mw-100::attr(href)").extract()
  84.         #     ]
  85.         # )
  86.         # inner_load = response.css("div.show-more ::attr(data-url)").get()
  87.         # if inner_load:
  88.         #     yield scrapy.Request(
  89.         #         url=inner_load, headers=self.headers, callback=self.parse_page_items
  90.         #     )
  91.         # next_page_no = response.css('a[aria-label="Next"]::attr(href)').get()
  92.         # if next_page_no:
  93.         #     self.params["start"] = next_page_no.split("&")[0].split("=")[-1]
  94.         #     next_url = self.base_url + urlencode(self.params)
  95.         #     print(next_url)
  96.         #     yield scrapy.Request(
  97.         #         url=next_url, headers=self.headers, callback=self.parse_page_items
  98.         #     )
  99.  
  100.         for i, link in enumerate(item_links):
  101.             yield scrapy.Request(
  102.                 url=link,
  103.                 headers=self.headers,
  104.                 callback=self.parse_product_details,
  105.             )
  106.  
  107.     def parse_product_details(self, response):
  108.         item = {}
  109.         json_text = (
  110.             response.css('script[type="application/ld+json"]')
  111.             .get()
  112.             .replace('<script type="application/ld+json">', "")
  113.             .replace("</script>", "")
  114.         )
  115.         json_blob = json.loads(json_text)
  116.         prod_id = response.css("div.container.product-detail::attr(data-pid)").get()
  117.         colors = response.css("button::attr(data-adobelaunchproductcolor)").extract()
  118.         sizes = response.css("li::attr(data-attr-value)").extract()
  119.         item["product_id"] = prod_id
  120.         item["product_brand"] = response.css("a.product-brand::text").get()
  121.         item["product_name"] = response.css("h1.product-name::text").get()
  122.         json_breadcrumbs_text = (
  123.             response.css('script[type="application/ld+json"]')
  124.             .extract()[-1]
  125.             .replace('<script type="application/ld+json">', "")
  126.             .replace("</script>", "")
  127.         )
  128.         bc_json_blob = json.loads(json_breadcrumbs_text)
  129.         item["categories"] = [
  130.             {f"category_{idx}": cat["name"]}
  131.             for idx, cat in enumerate(bc_json_blob["itemListElement"], 1)
  132.         ]
  133.         item["slug"] = json_blob["offers"]["url"].split(".com")[-1]
  134.         desc = json_blob["description"]
  135.         item["description"] = re.sub("<[^<]+?>", " ", html.unescape(desc))
  136.         item["product_variants"] = []
  137.         item["color"] = response.css(
  138.             "span.text2.color-value.attribute-displayValue::text"
  139.         ).get()
  140.         item["sizes"] = []
  141.         item["gender"] = ""
  142.         bc_li = [b["name"] for b in bc_json_blob["itemListElement"]]
  143.  
  144.         if "Women's Clothing" in bc_li:
  145.             item["gender"] = "Female"
  146.         elif "Men" in bc_li or "Men's" in bc_li:
  147.             item["gender"] = "Male"
  148.         else:
  149.             item["gender"] = "Female"
  150.         if (
  151.             "Kids" in bc_li
  152.             and any("Boys" in s for s in bc_li)
  153.             or any("Baby Boy" in s for s in bc_li)
  154.         ):
  155.             item["gender"] = "Boy"
  156.         elif (
  157.             "Kids" in bc_li
  158.             and any("Girls" in s for s in bc_li)
  159.             or any("Baby Girl" in s for s in bc_li)
  160.         ):
  161.             item["gender"] = "Girl"
  162.  
  163.         elif (
  164.             any("Kids" in s for s in bc_li)
  165.             and not any("Baby Girl" in s for s in bc_li)
  166.             and not any("Baby Boy" in s for s in bc_li)
  167.             and not any("Boys" in s for s in bc_li)
  168.             and not any("Girls" in s for s in bc_li)
  169.         ):
  170.             item["gender"] = "Kids"
  171.  
  172.         elif any("Accessories" in s for s in bc_li):
  173.             item["gender"] = ""
  174.  
  175.         else:
  176.             item["gender"] = ""
  177.  
  178.         price_json_text = (
  179.             response.css('script[type="text/javascript"]')
  180.             .extract()[2]
  181.             .replace('<script type="text/javascript">\npageDataObj = ', "")
  182.             .replace(";\n</script>", "")
  183.         )
  184.         price_json_blob = json.loads(price_json_text)
  185.         item["tag"] = price_json_blob["products"][0]["tags"]["feature_type"]
  186.         item["price"] = [
  187.             {
  188.                 "original_price": p["original_price"],
  189.                 "price": p["price"],
  190.                 "currency": json_blob["offers"]["priceCurrency"],
  191.             }
  192.             for p in price_json_blob["products"]
  193.         ]
  194.         variant_urls = []
  195.         for color in colors:
  196.             for i_size in sizes:
  197.                 variant_url = (
  198.                     response.url
  199.                     + "?dwvar_"
  200.                     + prod_id
  201.                     + "_color="
  202.                     + color.upper()
  203.                     + "&dwvar_"
  204.                     + prod_id
  205.                     + f"_size={i_size}&pid="
  206.                     + prod_id
  207.                 )
  208.                 variant_urls.append(variant_url)
  209.         item["images"] = json_blob["image"]
  210.         size_selector = response.css(
  211.             "ul.radio-group-list.size-attribute.swatch-display-three.show-size-dropdown"
  212.         )
  213.         if size_selector:
  214.             for s in size_selector.css("li"):
  215.                 all_size_var = s.css("::text").getall()
  216.                 if not s.css("[disabled]"):
  217.                     available = all_size_var
  218.                     clean = list(filter(None, [c.replace("\n", "") for c in available]))
  219.                     for out_si in clean:
  220.                         item["sizes"].append({"size": out_si, "status": "AVAILABLE"})
  221.                 else:
  222.                     out_of_stock = all_size_var
  223.                     clean = list(
  224.                         filter(None, [c.replace("\n", "") for c in out_of_stock])
  225.                     )
  226.                     for in_si in clean:
  227.                         item["sizes"].append({"size": in_si, "status": "NOT_AVAILABLE"})
  228.         clean_sizes_urls = []
  229.         if response.css("div.form-group.show-size-dropdown-holder"):
  230.             size_dropdown = response.css(
  231.                 "ul.radio-group-list.size-attribute.swatch-display-three ::text"
  232.             ).extract()
  233.             clean_sizes = list(
  234.                 filter(None, [s.replace("\n", "") for s in size_dropdown])
  235.             )
  236.             for dd_si in clean_sizes:
  237.                 if item["color"]:
  238.                     variant_url = (
  239.                         response.url
  240.                         + "?dwvar_"
  241.                         + prod_id
  242.                         + "_color="
  243.                         + item["color"].upper()
  244.                         + "&dwvar_"
  245.                         + prod_id
  246.                         + f"_size={dd_si}&pid="
  247.                         + prod_id
  248.                     )
  249.                     clean_sizes_urls.append(variant_url)
  250.         if variant_urls:
  251.             yield scrapy.Request(
  252.                 variant_urls[0],
  253.                 headers=self.headers,
  254.                 dont_filter=True,
  255.                 cb_kwargs={
  256.                     "item": item,
  257.                     "variant_urls": variant_urls[1:],
  258.                     "clean_sizes_urls": clean_sizes_urls,
  259.                 },
  260.                 callback=self.parse_product_variants,
  261.             )
  262.         elif clean_sizes_urls:
  263.             yield scrapy.Request(
  264.                 clean_sizes_urls[0],
  265.                 headers=self.headers,
  266.                 dont_filter=True,
  267.                 cb_kwargs={"item": item, "clean_sizes_urls": clean_sizes_urls[1:]},
  268.                 callback=self.parse_clean_sizes,
  269.             )
  270.         else:
  271.             yield item
  272.  
  273.     def parse_product_variants(self, response, item, variant_urls, clean_sizes_urls):
  274.         size = "".join(
  275.             list(
  276.                 filter(
  277.                     None,
  278.                     [
  279.                         s.replace("\n", "")
  280.                         for s in response.css("li").css("[selected] ::text").extract()
  281.                     ],
  282.                 )
  283.             )
  284.         )
  285.         disabled = (
  286.             response.css("li").css("[disabled]").css("[selected] ::text").getall()
  287.         )
  288.         final_price = ""
  289.         final_price = response.css(
  290.             "span.formatted_sale_price.formatted_price.js-final-sale-price.bfx-price.bfx-list-price::text"
  291.         ).get()
  292.         if final_price is None:
  293.             final_price = response.css(
  294.                 "span.formatted_sale_price.formatted_price.js-final-sale-price.bfx-price.bfx-sale-price::text"
  295.             ).get()
  296.         try:
  297.             old_price = response.css(
  298.                 "span.formatted_price.bfx-price.bfx-list-price ::text"
  299.             ).get()
  300.         except:
  301.             old_price = ""
  302.  
  303.         if not disabled:
  304.             item["product_variants"].append(
  305.                 {
  306.                     "color": response.css(
  307.                         "span.text2.color-value.attribute-displayValue::text"
  308.                     ).get(),
  309.                     "size": size,
  310.                     "status": "AVAILABLE",
  311.                     "final_price": final_price,
  312.                     "old_price": old_price,
  313.                 }
  314.             )
  315.         else:
  316.             item["product_variants"].append(
  317.                 {
  318.                     "color": response.css(
  319.                         "span.text2.color-value.attribute-displayValue::text"
  320.                     ).get(),
  321.                     "size": size,
  322.                     "status": "NOT_AVAILABLE",
  323.                     "final_price": final_price,
  324.                     "old_price": old_price,
  325.                 }
  326.             )
  327.         if len(variant_urls) > 0:
  328.             yield scrapy.Request(
  329.                 variant_urls.pop(0),
  330.                 headers=self.headers,
  331.                 dont_filter=True,
  332.                 cb_kwargs={
  333.                     "item": item,
  334.                     "variant_urls": variant_urls[1:],
  335.                     "clean_sizes_urls": clean_sizes_urls,
  336.                 },
  337.                 callback=self.parse_product_variants,
  338.             )
  339.         elif len(clean_sizes_urls) > 0:
  340.             yield scrapy.Request(
  341.                 clean_sizes_urls.pop(0),
  342.                 headers=self.headers,
  343.                 dont_filter=True,
  344.                 cb_kwargs={"item": item, "clean_sizes_urls": clean_sizes_urls[1:]},
  345.                 callback=self.parse_clean_sizes,
  346.             )
  347.         else:
  348.             yield item
  349.  
  350.     def parse_clean_sizes(self, response, item, clean_sizes_urls):
  351.         size = "".join(
  352.             list(
  353.                 filter(
  354.                     None,
  355.                     [
  356.                         s.replace("\n", "")
  357.                         for s in response.css("li").css("[selected] ::text").extract()
  358.                     ],
  359.                 )
  360.             )
  361.         )
  362.         disabled = (
  363.             response.css("li").css("[disabled]").css("[selected] ::text").getall()
  364.         )
  365.         final_price = ""
  366.         final_price = response.css(
  367.             "span.formatted_sale_price.formatted_price.js-final-sale-price.bfx-price.bfx-list-price::text"
  368.         ).get()
  369.         if final_price is None:
  370.             final_price = response.css(
  371.                 "span.formatted_sale_price.formatted_price.js-final-sale-price.bfx-price.bfx-sale-price::text"
  372.             ).get()
  373.         try:
  374.             old_price = response.css(
  375.                 "span.formatted_price.bfx-price.bfx-list-price ::text"
  376.             ).get()
  377.         except:
  378.             old_price = ""
  379.  
  380.         if not disabled:
  381.             item["product_variants"].append(
  382.                 {
  383.                     "color": item["color"],
  384.                     "size": size,
  385.                     "status": "AVAILABLE",
  386.                     "final_price": final_price,
  387.                     "old_price": old_price,
  388.                 }
  389.             )
  390.         else:
  391.             item["product_variants"].append(
  392.                 {
  393.                     "color": item["color"],
  394.                     "size": size,
  395.                     "status": "NOT_AVAILABLE",
  396.                     "final_price": final_price,
  397.                     "old_price": old_price,
  398.                 }
  399.             )
  400.         if len(clean_sizes_urls) > 0:
  401.             yield scrapy.Request(
  402.                 clean_sizes_urls.pop(0),
  403.                 headers=self.headers,
  404.                 dont_filter=True,
  405.                 cb_kwargs={"item": item, "clean_sizes_urls": clean_sizes_urls[1:]},
  406.                 callback=self.parse_clean_sizes,
  407.             )
  408.         else:
  409.             yield item
Advertisement
Add Comment
Please, Sign In to add comment