一、最近刚好想换手机,然后就想知道京东上心仪的手机价格如何,对比手机价格如何,以及相应的历史价格,然后就用Python requests+MySQLdb+smtplib爬取相关的数据
二、关于实现的主要步骤:
1、根据京东搜索页面,搜索某型号(如小米手机)的自营手机,得出该型号在京东自营上的链接url
2、根据该url,将小米手机的京东自营的所有种类的url均爬取下来,存在列表或者字典里
3、根据找到的所有商品的url,爬取商品对应的价格,商品的简介
4、将以上相关信息插入数据库,并根据数据库下一条目与上一条目的价格对比,若产品降价了,则发相关email到指定用户进行通知
三、Python实现
1、如何获得京东自营的某型号手机的大类url:
首先,在京东自营上搜索某手机时,出现的结果列表并不会显示该品牌手机的所有型号手机,如可能会显示小米5 黑色32G,但可能不会显示小米5 白色 32G这一型号,这一型号在我们点击小米5 黑色32G链接进入下一级页面后会有相应的链接,所以我们第一步先要爬出所有该品牌的大类链接,再由大类链接去找到所有的该品牌的所有机型。
脚本实现如下:调用webdriver的PhantonJS, 进行JS渲染,从而得到页面的html,再运用正则判断商店名是否为该品牌的自营手机店,从而确定抓取哪些url
def get_href_list(self, search_url, shop_name):'''得到京东某品牌自营的各种乐视手机的型号大类的url列表'''dv = webdriver.PhantomJS()a = search_urldv.get(a)time.sleep(4)html = dv.execute_script("return document.documentElement.outerHTML")aa = open('tey.txt','w+')aa.write(html)aa.close()bb = open('tey.txt' , 'r+')bbb = bb.read()bb.close()re_cmp = re.compile('<li class="gl-item" data-sku="(.*?)"', re.S)re_cmp_shop = re.compile('<div class="p-shop"(.*?)</div>', re.S)re_cmp_shop2 = re.compile('<a.*?>(.*?)</a>', re.S)cc = re.findall(re_cmp , bbb)aa = open('tey_1.txt','w+')for i in cc:aa.write(i + '\n')aa.close()return_href_list = []dd= re.findall(re_cmp_shop, bbb)aa = open('tey_2.txt','w+')print 'url is %s, shop name is %s'%(search_url,shop_name)for jj in range(len(dd)):ddd = re.findall(re_cmp_shop2, dd[jj])if len(ddd) !=0 and ddd[0] == shop_name :return_href_list.append('https://item.jd.com/' + cc[jj] + '.html')aa.write(ddd[0] + '\n' + 'https://item.jd.com/' + cc[jj] + '.html' + '\n\n')aa.close()self.return_list = return_href_listreturn return_href_list
2、抓到大类url后,需要进一步根据大类url,抓取该品牌所以机型Url,及相应的价格,简介等。首先也是对大类中的每个url进行JS渲染,再根据正则表达式抓取相应的简介,价格是抓包发现由特定的url进行交互实现的价格数值的传递,因此对该url进行调用即可得到特定机型的价格,(参数为特定机型的京东item id),最后将所有的信息存入一个列表中。
def get_href_info(self, href_list):
'''用来根据给定的大类url来找出相对应的所有不同型号的乐视手机的url及对应的规格说明及相应的价格'''final_href = []final_return_result = []count = 0for single_href_list in href_list:count += 1try:dv = webdriver.PhantomJS()a = single_href_listdv.get(a)time.sleep(4)html = dv.execute_script("return document.documentElement.outerHTML")aa = open('leshi_%s.txt'%count,'w+')aa.write(html)aa.close()re_compile_info = re.compile('<div class="sku-name">(.*?)</div>', re.S)full_info = re.findall(re_compile_info, html)[0]re_compile = re.compile('colorSize: (.*?)],', re.S)cc = re.findall(re_compile, html)[0]except:dv = webdriver.PhantomJS()a = single_href_listdv.get(a)time.sleep(4)html = dv.execute_script("return document.documentElement.outerHTML")aa = open('leshi_%s.txt'%count,'w+')aa.write(html)aa.close()re_compile_info = re.compile('<div class="sku-name">(.*?)</div>', re.S)full_info = re.findall(re_compile_info, html)[0]re_compile = re.compile('colorSize: (.*?)],', re.S)cc = re.findall(re_compile, html)[0]re_compile1 = re.compile('{(.*?)}', re.S)ccc = re.findall(re_compile1, cc)if len(ccc[0]) == 0:info_list = []info_list.append(full_info)info_list.append(re.findall(re.compile('.com/(.*?).html' ,re.S), a)[0])final_href.append(info_list)else:re_item_id_compile = re.compile('"SkuId":(.*?),', re.S)for i in ccc:item_id = re.findall(re_item_id_compile, i)[0]re_compile_info = re.compile('<div class="sku-name">(.*?)</div>', re.S)html = urllib.urlopen('https://item.jd.com/' + item_id + '.html')html = html.read()full_info = re.findall(re_compile_info, html)[0]info_string = full_infoinfo_list = []info_list.append(info_string)info_list.append(item_id)final_href.append(info_list)aa = open('leshi.txt','w+')for kk in final_href:hhhh = []print kk[0]print kk[1]try_time = 10while try_time>0:try:a = ['https://p.3.cn/prices/mgets?type=1&pduid=1557983493&pdpin=&pdbp=0&skuIds=J_','https://p.3.cn/prices/mgets?type=1&pduid=1557983493&pdbp=0&skuIds=J_', 'https://p.3.cn/prices/mgets?type=1&pduid=1557983493&skuIds=J_', 'https://p.3.cn/prices/mgets?type=1&area=1_72_4137_0&pdtk=&pduid=1557983493&pdpin=&pdbp=0&skuIds=J_']aaa = a[random.randint(0,4)] + str(kk[1])b=urllib.urlopen(aaa)c=b.read()d=eval(c)kk.append(d[0]['p'])breakexcept:try_time = try_time - 1print 'fail %s time in href %s'%((10 -try_time), str(kk[1]))time.sleep(random.randint(1,3))if len(kk) == 2:kk.append('Not captured')aa.write(kk[0] + ' ' + 'https://item.jd.com/' + str(kk[1]) + '.html' + ' ' + kk[2])aa.write('\n\n')hhhh.append(str(kk[1]))hhhh.append(kk[0])hhhh.append(kk[2])hhhh.append('https://item.jd.com/' + str(kk[1]) + '.html')hhhh.append( time.strftime("%Y-%m-%d,%H:%M:%p", time.localtime()))final_return_result.append(hhhh)aa.close()self.return_list = final_return_resultreturn final_return_result
3、调用Mysql数据库将相关数据插入数据库:
使用的MySQLdb数据库,需要注意以下几点:
(1)、注意数据库的编码,默认为latin编码,这对中文编码是会存在异常的,需要将其修改为utf-8编码,具体修改是在MYSQL的配置文件里,就不赘述了;
(2)、MYSQL插入表时不支持变量插入,需要自己实现做好处理,表名的各事业有相关要求
(3)、执行语句时,如插入语句,对变量的格式(带”的字符创或不带”的整型)需要自己多留意
def connect_sql(self, data_list, databse_name,):'''将爬到的相关数据插入数据库'''conn = MySQLdb.connect(host = 'localhost', user = 'root', passwd = '', charset = 'utf8')cur = conn.cursor()database_create = 'create database if not exists %s'%databse_namecur.execute(database_create)conn.select_db(databse_name)table_name = 'price_' + time.strftime("%Y_%m_%d_%H_%M_%p", time.localtime())cur.execute('create table %s (item_id varchar(20) primary key not null, machine_name varchar(100),machine_price varchar(50),item_url varchar(50), time varchar(50))'%table_name)conn.commit()for ii in data_list:try:insert_sql = 'insert ignore %s values(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\')'%(table_name, ii[0], ii[1].decode('gbk').encode('utf-8'), ii[2], ii[3], ii[4])cur.execute(insert_sql)except:insert_sql = 'insert ignore %s values(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\')'%(table_name, ii[0], ii[1].encode('utf-8'), ii[2], ii[3], ii[4])cur.execute(insert_sql)conn.commit()cur.close()conn.close()
4、邮件通知事件:
从数据库的表中select数据,得到价格,与之前时间的价格对比,若有降价,发送email
msg = MIMEMultipart()
msg[‘from’] = ‘**@163.com’
msg[‘to’] = ‘***@qq.com’
email_info = u’%s 降价了,现在价格是%s 元,速速买’%(name, aaa[0][0])
msg[‘subject’] = Header(email_info,’utf-8’)
server = smtplib.SMTP(‘smtp.163.com’)
server.login(‘@163.com’,’***’)
error=server.sendmail(msg[‘from’], msg[‘to’],msg.as_string())
server.close
5、之后的定时爬取价格:
第一次完成爬取数据后,之后需要定时的爬取价格,由于手机型号,url,简介等都在第一次已经确定了,故而之后只需要根据这些信息去爬取可能会变化的价格就OK了,所以写了个函数实现价格爬取
根据爬去的商品的京东id,去调用京东的特定url,返回价格,并在数据库建立新表,插入数据库,其他的插入项如id等则等同于之前。
def get_price(self, all_info_list,databse_name):'''根据数据库已有的一个表数据来爬下一个时间的商品价格'''cur_time = time.strftime("%Y_%m_%d_%H_%M_%p", time.localtime())agen_list = ['Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0', 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0','Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko)', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0']hea ={'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0','Host':'p.3.cn','Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3','Accept-Encoding':'gzip, deflate','Content-Length':'94'}for i in all_info_list:item_id = i[0]try_time = 10while try_time>0:try:a = ['https://p.3.cn/prices/mgets?type=1&pduid=1557983493&pdpin=&pdbp=0&skuIds=J_','https://p.3.cn/prices/mgets?type=1&pduid=1557983493&pdbp=0&skuIds=J_', 'https://p.3.cn/prices/mgets?type=1&pduid=1557983493&skuIds=J_', 'https://p.3.cn/prices/mgets?type=1&area=1_72_4137_0&pdtk=&pduid=1557983493&pdpin=&pdbp=0&skuIds=J_']aa = a[random.randint(0,4)] + str(item_id)hea['User-Agent'] = agen_list[random.randint(0,4)]b=requests.get(aa, headers=hea)c=b.textd=eval(c)i[2] = d[0]['p']i[4] = cur_timebreakexcept:try_time = try_time - 1time.sleep(random.randint(1,3))print 'fail %s time in href %s'%((10 - try_time),i[3])if try_time == 0:i[2] = 'Not captured'self.connect_sql(all_info_list,databse_name)