Advertisement
Artem78

tailor4less_scraper.py

Feb 4th, 2019
141
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.92 KB | None | 0 0
  1. #!/usr/bin/python3
  2. #coding: utf8
  3.  
  4. import requests
  5. from pprint import pprint
  6. from random import randint, uniform
  7. from time import sleep
  8. from pyquery import PyQuery as pq
  9. import csv
  10. import itertools
  11. import re, demjson
  12. import decimal
  13. from random import uniform
  14.  
  15.  
  16. # The same as range() function but for decimals
  17. class drange():
  18.     def __init__(self, start, stop, step = 1):
  19.         self.start = decimal.Decimal(start)
  20.         self.stop = decimal.Decimal(stop)
  21.         self.step = decimal.Decimal(step)
  22.         self.value = self.start
  23.  
  24.     def __iter__(self):
  25.         self.value = self.start
  26.         return self
  27.  
  28.     def __next__(self):
  29.         if self.step > 0 and self.value < self.stop or self.step < 0 and self.value > self.stop:
  30.             current = self.value
  31.             self.value += self.step
  32.             return current
  33.         else:
  34.             raise StopIteration()
  35.  
  36.     def __len__(self):
  37.         return max(0, int((self.stop - self.start) / self.step + 1))
  38.  
  39.  
  40.  
  41.  
  42. # Settings
  43. #ranges = {
  44. #   'man': {
  45. #       'weights': range(126, 226 + 1, 1),
  46. #       'height': drange(45, 150 + 0.5, 0.5),
  47. #   },
  48. #
  49. #   'woman': {
  50. #       'weights': range(126, 200 + 1, 1),
  51. #       'height': drange(30, 140 + 0.5, 0.5),
  52. #   },
  53. #}
  54.  
  55. ranges = {
  56.     'man': {
  57.         'weights': range(126, 127 + 1, 1),
  58.         'heights': drange(45, 45 + 0.5, 0.5),
  59.     },
  60.  
  61.     'woman': {
  62.         'weights': range(126, 126 + 1, 1),
  63.         'heights': drange(30, 30 + 0.5, 0.5),
  64.     },
  65. }
  66.  
  67.  
  68.  
  69.  
  70. for gender in ('woman', 'man'):
  71.     total = 0
  72.     print('==============================')
  73.     print('*** Start scraping for %s ***' % gender)
  74.     with open('measurements_%s.csv' % gender, 'wt') as f:
  75.  
  76.         # Prepare object for queries
  77.         if gender == 'man':
  78.             base_url = 'https://www.tailor4less.com/en-uk'
  79.         elif gender == 'woman':
  80.             base_url = 'https://www.sumissura.com/en-uk'
  81.  
  82.         requests.packages.urllib3.disable_warnings()
  83.         headers = {
  84.             "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
  85.             "Accept-Language": "en-US;q=0.6,en;q=0.4",
  86.             "Referer": base_url,
  87.             "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36"
  88.         }
  89.         session = requests.session()
  90.         session.headers = headers
  91.  
  92.  
  93.         writer = None
  94.  
  95.  
  96.         # We need to add various kinds of products to cart due to get all available measurements options in the calculator
  97.         if gender == 'man':
  98.             products = (
  99.                 4839,
  100.                 4854,
  101.                 4746,
  102.                 646,
  103.                 616,
  104.                 378,
  105.                 5856,
  106.                 6239,
  107.                 5584,
  108.                 2092
  109.             )
  110.  
  111.         elif gender == 'woman':
  112.             products = (
  113.                 7053,
  114.                 6809,
  115.                 7315,
  116.                 7055,
  117.                 6420,
  118.                 6969,
  119.                 7652,
  120.                 7660,
  121.                 7314,
  122.                 7129,
  123.                 6713,
  124.                 7559,
  125.                 7045,
  126.                 7045,
  127.                 6978,
  128.                 438,
  129.                 7563,
  130.                 442,
  131.                 7562
  132.             )
  133.  
  134.         print('Adding products to cart...')
  135.         for product in products:
  136.             url = '%s/feed/buy/%d' % (base_url, product)
  137.             r = session.get(url)
  138.         print('Done!')
  139.  
  140.  
  141.  
  142.         # Recieving all available constitutions
  143.         print('Loading constitutions variants...')
  144.         constitutions = {}
  145.         url = '%s/checkout/measures/?step=start' % (base_url)
  146.         r = session.get(url)
  147.         dom = pq(r.text)
  148.         for elem in dom('.constitutions .constitution input[type="radio"]'):
  149.             elem = pq(elem)
  150.             name, value = elem.attr('name'), elem.attr('value')
  151.             if not constitutions.get(name):
  152.                 constitutions[name] = []
  153.             constitutions[name].append(value)
  154.         print('Constitutions:')
  155.         pprint(constitutions)
  156.         print('Done!')
  157.  
  158.  
  159.         default_params = {}
  160.         for elem in dom('.process .inputs .input > input'):
  161.             elem = pq(elem)
  162.             name, value = elem.attr('name'), elem.attr('value')
  163.             default_params[name] = value
  164.         #pprint(default_params)
  165.  
  166.  
  167.  
  168.         #print(r.text)
  169.         m = re.findall(r"var options = (\{[^=]*\});\s*<\/script>", r.text, re.DOTALL)
  170.         options = demjson.decode(m[0])
  171.         #pprint(options)
  172.         #input()
  173.         #continue
  174.  
  175.  
  176.  
  177.         # Loop for all combinations of heights, weights and constitutions
  178.         for height in ranges[gender]['heights']:
  179.             for weight in ranges[gender]['weights']:
  180.                     keys = sorted(constitutions)
  181.                     constitutions_combs = [dict(zip(keys, p)) for p in itertools.product(*(constitutions[k] for k in keys))]
  182.  
  183.  
  184.                     # Constitutions does not affect to measurements for woman (seems that it is site bug)
  185.                     if gender == 'woman':
  186.                         constitutions_combs = [{}]
  187.  
  188.  
  189.                     for constitutions_comb in constitutions_combs:
  190.                         print('Height: %s, Weight: %s' % (height, weight))
  191.                         print('Constitution:')
  192.                         pprint(constitutions_comb)
  193.  
  194.                         err = False
  195.                         while True:
  196.                             try:
  197.                                 url = '%s/%s/measures/estimate' % (base_url, gender)
  198.                                 params = default_params.copy()
  199.                                 params.update({
  200.                                     'weight_units':'kg',
  201.                                     'length_units':'cm',
  202.                                     'weight':weight,
  203.                                     'height':height,
  204.                                 })
  205.                                 params.update(constitutions_comb)
  206.                                 r = session.get(url, params=params, timeout=20)
  207.                                 res = r.json()
  208.                                 err = False
  209.                             except Exception as msg:
  210.                                 print('ERROR:', msg)
  211.                                 err = True
  212.                                 sleep(uniform(5,30))
  213.                                 print('Retry')
  214.  
  215.                             if not err:
  216.                                 break;
  217.  
  218.  
  219.  
  220.  
  221.                         # Calculate average min and max values for each measurement
  222.                         for measure_type in list(res):
  223.                             range_text = ''
  224.  
  225.                             try:
  226.                                 measure = float(res[measure_type])
  227.  
  228.                                 if measure > 0:
  229.                                     optionals = options['measures']['optional']
  230.                                     requireds = options['measures']['required']
  231.                                     rng = None
  232.  
  233.  
  234.                                     if optionals:
  235.                                         if measure_type in optionals:
  236.                                             rng = optionals[measure_type]
  237.  
  238.  
  239.                                     if requireds:
  240.                                         if measure_type in requireds:
  241.                                             rng = requireds[measure_type]
  242.  
  243.  
  244.                                     if rng:
  245.                                         if ('range' in rng) and len(rng.get('range')) == 2:
  246.                                             min = measure * (rng['range'][0] / 100 + 1)
  247.                                             max = measure * (rng['range'][1] / 100 + 1)
  248.                                         else:
  249.                                             min = measure * (rng['range']['tolerance']['normal']['max'][0] / 100 + 1)
  250.                                             max = measure * (rng['range']['tolerance']['normal']['max'][1] / 100 + 1)
  251.  
  252.  
  253.                                         min = round(min)
  254.                                         max = round(max)
  255.  
  256.                                         range_text = 'Usually between %dcm and %dcm' % (min, max)
  257.                             except Exception as msg:
  258.                                 print('ERROR:', msg)
  259.                                 range_text = ''
  260.  
  261.                             res[measure_type + '_range'] = range_text
  262.  
  263.  
  264.                         print('Calculated measurements:')
  265.                         pprint(res)
  266.  
  267.  
  268.  
  269.                         # Prepare data for writing in csv
  270.                         data = {
  271.                             'in': params,
  272.                             'out': res
  273.                         }
  274.  
  275.  
  276.                         if (not writer):
  277.                             # Now we know names of all columns and can write them in CSV
  278.                             writer = csv.DictWriter(f, ['height', 'length_units', 'weight', 'weight_units'] + sorted(constitutions.keys() if gender != 'woman' else []) + sorted(data['out'].keys()))
  279.                             writer.writeheader()
  280.  
  281.  
  282.                         all_data = data['in'].copy()
  283.                         all_data.update(data['out'])
  284.                         writer.writerow(all_data)
  285.  
  286.  
  287.                         total += 1
  288.                         print('Progress: %d/%d' % (total, we))
  289.                         print()
  290.  
  291.                         f.flush()
  292.  
  293.     print()
  294.     print('*** Scraping for %s done! ***' % gender)
  295.     print()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement