From db7444f278540d2c15b61e9b0b3fa853f85eb75a Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Sun, 22 May 2016 03:11:18 +0200 Subject: [PATCH 01/11] Fixed the scraping and parsing of Blocket. --- parser.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/parser.py b/parser.py index 7c4c2cb..05c5c85 100644 --- a/parser.py +++ b/parser.py @@ -1,6 +1,7 @@ from bs4 import BeautifulSoup from urllib2 import urlopen import shelve +import re SEARCH_TERM = 'canon' CITY = 'stockholm' @@ -12,11 +13,11 @@ def make_soup(url): def get_item_links(): soup = make_soup("http://www.blocket.se/" + CITY + "?q=" + SEARCH_TERM) - items = [item.a["href"] for item in soup.findAll("div", { "class" : "item_row" })] + items = [item.a["href"] for item in soup.findAll("article", class_ = re.compile("item_row"))] return items def get_new_items(items): - sh = shelve.open('urlshelve', writeback=True) + sh = shelve.open('urlshelve2', writeback=True) new_urls = [] try: urls = sh['urls'] @@ -35,4 +36,4 @@ def parse_page(): new_items = get_new_items(items) -parse_page() \ No newline at end of file +parse_page() From 3c3c5e9614bb6f5d36d1929efd223c3fa58d7753 Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Sun, 22 May 2016 03:20:33 +0200 Subject: [PATCH 02/11] Reverted to old shelve name now that I've confirmed it works again. --- parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.py b/parser.py index 05c5c85..8db846d 100644 --- a/parser.py +++ b/parser.py @@ -17,7 +17,7 @@ def get_item_links(): return items def get_new_items(items): - sh = shelve.open('urlshelve2', writeback=True) + sh = shelve.open('urlshelve', writeback=True) new_urls = [] try: urls = sh['urls'] From d0fbbdf2ce6ee96d50f653ae69cbfbac80e153d9 Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Sun, 22 May 2016 03:30:05 +0200 Subject: [PATCH 03/11] Support for multiple searches and reading searchparameters from file added. --- parser.py | 8 ++++++-- searchoptions.txt | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 searchoptions.txt diff --git a/parser.py b/parser.py index 8db846d..8d384f9 100644 --- a/parser.py +++ b/parser.py @@ -35,5 +35,9 @@ def parse_page(): items = get_item_links() new_items = get_new_items(items) - -parse_page() +with open("searchoptions.txt") as file: + for line in file: + options = line.split(";") + CITY = options[0] + SEARCH_TERM = options[1] + parse_page() diff --git a/searchoptions.txt b/searchoptions.txt new file mode 100644 index 0000000..6167679 --- /dev/null +++ b/searchoptions.txt @@ -0,0 +1,3 @@ +lund;cykel +lund;motorcykel +malmo;bil From 92961fa0d47fc4ce3dce86fb844616c6d2791e68 Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Mon, 23 May 2016 00:30:12 +0200 Subject: [PATCH 04/11] Added category filter. --- parser.py | 21 ++++++++++++++++++--- searchoptions.txt | 6 +++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/parser.py b/parser.py index 8d384f9..e4cd653 100644 --- a/parser.py +++ b/parser.py @@ -5,6 +5,7 @@ SEARCH_TERM = 'canon' CITY = 'stockholm' +CATEGORY = '0' def make_soup(url): html = urlopen(url).read() @@ -12,7 +13,7 @@ def make_soup(url): return soup def get_item_links(): - soup = make_soup("http://www.blocket.se/" + CITY + "?q=" + SEARCH_TERM) + soup = make_soup("http://www.blocket.se/" + CITY + "?q=" + SEARCH_TERM + "&cg=" + CATEGORY) items = [item.a["href"] for item in soup.findAll("article", class_ = re.compile("item_row"))] return items @@ -34,10 +35,24 @@ def get_new_items(items): def parse_page(): items = get_item_links() new_items = get_new_items(items) + return new_items + +def get_categories(): + soup = make_soup("http://www.blocket.se/hela_sverige?") + items = soup.find_all("option", class_ = True) + cats = {} + for item in items: + if item["class"] != ["blind"]: + cats[unicode(item.string).lower()] = item["value"] + return cats + +categories = get_categories() with open("searchoptions.txt") as file: for line in file: - options = line.split(";") + options = line.rstrip().split(";") CITY = options[0] - SEARCH_TERM = options[1] + #converts category string to category number + CATEGORY = categories[options[1].lower()] + SEARCH_TERM = options[2] parse_page() diff --git a/searchoptions.txt b/searchoptions.txt index 6167679..03d7366 100644 --- a/searchoptions.txt +++ b/searchoptions.txt @@ -1,3 +1,3 @@ -lund;cykel -lund;motorcykel -malmo;bil +lund;cyklar;cykel +lund;motorcyklar;yamaha +malmo;bilar;subaru From d9b0d49e5e4c8a7ee461b0b1722598faffc15177 Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Wed, 25 May 2016 00:17:51 +0200 Subject: [PATCH 05/11] Added scraping and filtering of price --- parser.py | 50 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/parser.py b/parser.py index e4cd653..82b0119 100644 --- a/parser.py +++ b/parser.py @@ -2,38 +2,74 @@ from urllib2 import urlopen import shelve import re +import sys SEARCH_TERM = 'canon' CITY = 'stockholm' CATEGORY = '0' +MIN_PRICE_DEFAULT = 0 +MAX_PRICE_DEFAULT = sys.maxint +MIN_PRICE = MIN_PRICE_DEFAULT +MAX_PRICE = MAX_PRICE_DEFAULT def make_soup(url): html = urlopen(url).read() soup = BeautifulSoup(html, "lxml") return soup -def get_item_links(): +def get_items(): soup = make_soup("http://www.blocket.se/" + CITY + "?q=" + SEARCH_TERM + "&cg=" + CATEGORY) - items = [item.a["href"] for item in soup.findAll("article", class_ = re.compile("item_row"))] + urls = [link.a["href"] for link in soup.findAll("article", class_ = re.compile("item_row"))] + items = [] + for url in urls: + items.append(get_item_details(url)) return items +def price_change(item, old_price): + #to be implemented + print "Price change! " + item["title"] + " Old price: " + old_price + " New price: " + item["price"] + print item["url"] + +def get_item_details(url): + soup = make_soup(url) + price_container = soup.find("div", id = "price_container") + try: + price_string = price_container.find("div", id = "vi_price").string + price = int(re.sub("\D", "", price_string)) #removes punctuation and whitespace and converts to int + except: + price = -1 + try: + old_price_string = price_container.find("span", class_ = "text-secondary").s.string + old_price = int(re.sub("\D", "", old_price_string)) + except: + old_price = -1 + title = unicode(soup.find("h1").string).strip() + return {"url": url, "price": price, "old_price": old_price, "title": title} + def get_new_items(items): - sh = shelve.open('urlshelve', writeback=True) + sh = shelve.open('urlshelve3', writeback=True) new_urls = [] try: urls = sh['urls'] except: - urls = sh['urls'] = set() + urls = sh['urls'] = {} for item in items: - if not item in urls: - sh['urls'].add(item) + if not item["url"] in urls.iterkeys(): + sh['urls'][item['url']] = item #{"price": item["price"], "title": item["title"]} new_urls.append(item) print item + else: + price = item["price"] + old_price = item["old_price"] + if old_price != -1 and old_price != price: + last_logged_price = sh['urls'][item['url']]["price"] + if last_logged_price != item["price"]: + price_change(item, last_logged_price) sh.close() return new_urls def parse_page(): - items = get_item_links() + items = get_items() new_items = get_new_items(items) return new_items From 14b357d1e9f980e05e30e2fb8d522b14545908be Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Wed, 25 May 2016 16:57:01 +0200 Subject: [PATCH 06/11] Improved performance by scraping title and price info directly from search results. Now executes 4x faster according to my measurements. --- parser.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/parser.py b/parser.py index 82b0119..f792738 100644 --- a/parser.py +++ b/parser.py @@ -19,10 +19,10 @@ def make_soup(url): def get_items(): soup = make_soup("http://www.blocket.se/" + CITY + "?q=" + SEARCH_TERM + "&cg=" + CATEGORY) - urls = [link.a["href"] for link in soup.findAll("article", class_ = re.compile("item_row"))] + objects = [obj for obj in soup.findAll("article", class_ = re.compile("item_row"))] items = [] - for url in urls: - items.append(get_item_details(url)) + for obj in objects: + items.append(get_item_details(obj)) return items def price_change(item, old_price): @@ -30,25 +30,30 @@ def price_change(item, old_price): print "Price change! " + item["title"] + " Old price: " + old_price + " New price: " + item["price"] print item["url"] -def get_item_details(url): - soup = make_soup(url) - price_container = soup.find("div", id = "price_container") +def get_item_details(obj): + a = obj.h1.a + url = a["href"] + title = unicode(a.string) + prices = obj.p.contents try: - price_string = price_container.find("div", id = "vi_price").string - price = int(re.sub("\D", "", price_string)) #removes punctuation and whitespace and converts to int + price_string = prices[0] + price = int(re.sub("\D", "", price_string)) #removes non-digits and converts to int except: price = -1 - try: - old_price_string = price_container.find("span", class_ = "text-secondary").s.string - old_price = int(re.sub("\D", "", old_price_string)) - except: - old_price = -1 - title = unicode(soup.find("h1").string).strip() + if len(prices) == 2: + soup = make_soup(url) + price_container = soup.find("div", id = "price_container") + try: + old_price_string = price_container.find("span", class_ = "text-secondary").s.string + old_price = int(re.sub("\D", "", old_price_string)) + except: + old_price = -1 + else: old_price = -1 return {"url": url, "price": price, "old_price": old_price, "title": title} def get_new_items(items): sh = shelve.open('urlshelve3', writeback=True) - new_urls = [] + new_items = [] try: urls = sh['urls'] except: @@ -56,7 +61,7 @@ def get_new_items(items): for item in items: if not item["url"] in urls.iterkeys(): sh['urls'][item['url']] = item #{"price": item["price"], "title": item["title"]} - new_urls.append(item) + new_items.append(item) print item else: price = item["price"] @@ -66,7 +71,7 @@ def get_new_items(items): if last_logged_price != item["price"]: price_change(item, last_logged_price) sh.close() - return new_urls + return new_items def parse_page(): items = get_items() From 213e6638c7029c6ebc46887185b05eed7515279d Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Wed, 25 May 2016 19:24:06 +0200 Subject: [PATCH 07/11] Now actually filters based on price, instead of just recording it. :) --- parser.py | 15 +++++++++++++-- searchoptions.txt | 6 +++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/parser.py b/parser.py index f792738..053d7fc 100644 --- a/parser.py +++ b/parser.py @@ -3,11 +3,12 @@ import shelve import re import sys +from pushbullet import Pushbullet SEARCH_TERM = 'canon' CITY = 'stockholm' CATEGORY = '0' -MIN_PRICE_DEFAULT = 0 +MIN_PRICE_DEFAULT = -1 MAX_PRICE_DEFAULT = sys.maxint MIN_PRICE = MIN_PRICE_DEFAULT MAX_PRICE = MAX_PRICE_DEFAULT @@ -22,7 +23,9 @@ def get_items(): objects = [obj for obj in soup.findAll("article", class_ = re.compile("item_row"))] items = [] for obj in objects: - items.append(get_item_details(obj)) + item = get_item_details(obj) + if item["price"] >= MIN_PRICE and item["price"] <= MAX_PRICE: + items.append(item) return items def price_change(item, old_price): @@ -96,4 +99,12 @@ def get_categories(): #converts category string to category number CATEGORY = categories[options[1].lower()] SEARCH_TERM = options[2] + try: + MIN_PRICE = int(options[3]) + except: + MIN_PRICE = MIN_PRICE_DEFAULT + try: + MAX_PRICE = int(options[4]) + except: + MAX_PRICE = MAX_PRICE_DEFAULT parse_page() diff --git a/searchoptions.txt b/searchoptions.txt index 03d7366..8fd97f7 100644 --- a/searchoptions.txt +++ b/searchoptions.txt @@ -1,3 +1,3 @@ -lund;cyklar;cykel -lund;motorcyklar;yamaha -malmo;bilar;subaru +lund;cyklar;cykel;700;3500 +lund;motorcyklar;yamaha;10000;25000 +malmo;bilar;subaru;17000;55000 From cb7375f6ca2d86254d0b647f5cbb46d780fd069f Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Wed, 25 May 2016 20:06:54 +0200 Subject: [PATCH 08/11] Added support for Pushbullet. Can now send notifications of new search matches to various devices. --- parser.py | 24 +++++++++++++++++++++++- searchoptions.txt | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/parser.py b/parser.py index 053d7fc..ed2aee0 100644 --- a/parser.py +++ b/parser.py @@ -12,6 +12,8 @@ MAX_PRICE_DEFAULT = sys.maxint MIN_PRICE = MIN_PRICE_DEFAULT MAX_PRICE = MAX_PRICE_DEFAULT +PB_ACTIVE = False +API_KEY = "" def make_soup(url): html = urlopen(url).read() @@ -90,8 +92,25 @@ def get_categories(): cats[unicode(item.string).lower()] = item["value"] return cats +def push_item(items): + pb = Pushbullet(API_KEY) + for item in items: + if item["price"] == -1: + price_info = "Inget pris: " + else: + price_info = str(item["price"]) + ":- | " + push = pb.push_link(price_info + item["title"], item["url"]) + categories = get_categories() +try: + with open("api_key.txt") as pb_api: + API_KEY = pb_api.read().rstrip() + if len(API_KEY) > 25: + PB_ACTIVE = True +except: + raise + with open("searchoptions.txt") as file: for line in file: options = line.rstrip().split(";") @@ -107,4 +126,7 @@ def get_categories(): MAX_PRICE = int(options[4]) except: MAX_PRICE = MAX_PRICE_DEFAULT - parse_page() + item_list = parse_page() + + if PB_ACTIVE: + push_item(item_list) diff --git a/searchoptions.txt b/searchoptions.txt index 8fd97f7..412b453 100644 --- a/searchoptions.txt +++ b/searchoptions.txt @@ -1,3 +1,4 @@ lund;cyklar;cykel;700;3500 lund;motorcyklar;yamaha;10000;25000 malmo;bilar;subaru;17000;55000 +skane;Lastbil, Truck & Entreprenad;handtruck From 3ec7e6fe409631ae94990f879f44911a46597845 Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Wed, 25 May 2016 20:11:14 +0200 Subject: [PATCH 09/11] Changed the name of 'urlshelve3' back to 'urlshelve'. --- parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.py b/parser.py index ed2aee0..4b5851d 100644 --- a/parser.py +++ b/parser.py @@ -57,7 +57,7 @@ def get_item_details(obj): return {"url": url, "price": price, "old_price": old_price, "title": title} def get_new_items(items): - sh = shelve.open('urlshelve3', writeback=True) + sh = shelve.open('urlshelve', writeback=True) new_items = [] try: urls = sh['urls'] From d3175f4cda92f91e04bc5a493736ab44ea63d789 Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Wed, 25 May 2016 20:32:11 +0200 Subject: [PATCH 10/11] Implemented the price change watchdog, for real this time. --- parser.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/parser.py b/parser.py index 4b5851d..41d6b68 100644 --- a/parser.py +++ b/parser.py @@ -31,9 +31,10 @@ def get_items(): return items def price_change(item, old_price): - #to be implemented - print "Price change! " + item["title"] + " Old price: " + old_price + " New price: " + item["price"] - print item["url"] + print "Price change! " + item["title"] + " Old price: " + old_price + " New price: " + item["price"] + " URL: " + item["url"] + if PB_ACTIVE: + pb = Pushbullet(API_KEY) + pb.push_link("Price changed from " + str(old_price) + ":- to " + str(item["price"]) + ":- | " + item["title"], item["url"]) def get_item_details(obj): a = obj.h1.a @@ -65,7 +66,7 @@ def get_new_items(items): urls = sh['urls'] = {} for item in items: if not item["url"] in urls.iterkeys(): - sh['urls'][item['url']] = item #{"price": item["price"], "title": item["title"]} + sh['urls'][item['url']] = item new_items.append(item) print item else: @@ -73,8 +74,9 @@ def get_new_items(items): old_price = item["old_price"] if old_price != -1 and old_price != price: last_logged_price = sh['urls'][item['url']]["price"] - if last_logged_price != item["price"]: - price_change(item, last_logged_price) + dif = last_logged_price - item["price"] + if dif >= 100 and dif * 1.0 / last_logged_price >= 0.1 : + price_change(item, min(last_logged_price, old_price)) sh.close() return new_items @@ -96,7 +98,7 @@ def push_item(items): pb = Pushbullet(API_KEY) for item in items: if item["price"] == -1: - price_info = "Inget pris: " + price_info = "No price: " else: price_info = str(item["price"]) + ":- | " push = pb.push_link(price_info + item["title"], item["url"]) From 1b958b68970397c1d3f3489a5cb8a57265f1fcca Mon Sep 17 00:00:00 2001 From: hnrklssn Date: Wed, 25 May 2016 20:35:47 +0200 Subject: [PATCH 11/11] urlshelve.db is now only urlshelve, for some reason. --- urlshelve | Bin 0 -> 53248 bytes urlshelve.db | Bin 110592 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 urlshelve delete mode 100644 urlshelve.db diff --git a/urlshelve b/urlshelve new file mode 100644 index 0000000000000000000000000000000000000000..12dad14a278aa8c28b9e98020da58abb75eec395 GIT binary patch literal 53248 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%0ArV zn&81$9zN;q!%E=xS;Qi-u}dLBl4}>GPe5efz(AT2B zXrB2w>)|AW>H`=|j=R?{qS9)ztky%f92OkM&sNy?7GUmn2T$#E;*b|TkSr=)xxej0-r*i4SaP&mIqV+o|grO}zJl_Yf&F3^i?TWCU zcGQDYfCiGqu}-9TI_DV&W;_^gp1PkU_a@mN1iK-jKgO&rw{_q z#={|n00E#oQ5Jw&rbd4&j86F7!oOwjB|7+5a}#<%N-N@08C2I98E*(3Z&^TJ9Hh^P z0~GY|RMT}S)VestP%4FN6?cemU*dnp{lozH&*$d;?a$18kqMw{E`d7MK5hV=0E}w# zoM&GAMe%=$gr@YXBZGzosHaD<5?WWgM!$GQnU1&DL^UnUUaJM9H;K>F^Q7=S`aD@q zY)Fv$*hu@+MkP5mFIudJELQ3c-Nlipwl99S*FX?+2|Pqe;M4C;KZf*g^IflmSk#c#rXZR2L6@Y-~P>w;u5p8ujVX zu!A|w9BI@X)cv<5L36WMH}ht~*`E5EpWz=RI&rvMW4n|RDJcaD?T zNGlPM_5t0~B|mRA9gz^mNPbz{8JiFF@bIC#_m*(#SDZWzi(ZYSgwVrn_;eqB>uVo$|JvN#8-M$U zzs>)?X#cOz)7NYE|Ga$9&qe>7`A_BVe|)09|D~?}{*kBSeDpg%#V4cYKKizf|KXN? zf9t@GfAAON|NrvxkM+O5uD}12Kcm0@qks9Ygr3^L{}g&Yz5mPld;j|{$$uZ%c|ZL{ zrDN{rb(}YUL&y2?*X;L2#J|SxUcRUEzVUY*{pIui@xRe^XukOsS)T`XeSYwPe*Xt| z^!r=y==yx{@}J8(H2?Acm38>Rf7bPR|1bVC`ToOL*Ym-1UH>0_OTT~fKYU%r|H&We z?|<<7I?fw^Pw9N|L!;~YSCsw_eoMc9@HcecAN|@d$lt%P>-@G|&o?fW&L8}y{{GoJ zI)1JD#owX)y!`9>{fB>6*P-@Dza-;)dZ^$3u&v|&#;K*}xApsvepA1nc57b#Ri*#K zQ(cFbf2j0~8|US=(*L_wu6}Y)8 z{rfuaM`uds2X^0Iepko&s{Q`cUo5Bp)_Yb?20G5IcKJCSNB@UL$9dzqp0A_d()B;P zt@5++U8TSIpOl{Wef|AgC%Rv6SUG(8>$?7*+V3~I`ui_Oy1zfMa{lJ8l=C%?&f4GC z@!xz~`TgjwuICSaUdR9HP{0514|M#qP}j5hca+W#{)z6(xbdg`e&bBn;Ya^P&(~MK zqVvA-p??4VcXZv(eoyDE*>!%?p6fGvuHXC>{r#i=sN>xFy&I5!e*90%`8xX7I{wRj-QP1SC+{!n_|0Q0Kfi40f2zO#!5=C;{|{K$hW`Kn literal 0 HcmV?d00001 diff --git a/urlshelve.db b/urlshelve.db deleted file mode 100644 index ee932dbbe499a1d23a47a09d55a6b456f099784d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 110592 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