webmoney兑换(用爬虫技术能做到哪些有趣的事情)
专栏
2024-01-13 14:18
351
目录- webmoney兑换,用爬虫技术能做到哪些有趣的事情?
- 俄罗斯的互联网行业发展如何?
- 如何实现一键调用以太坊智能合约?
- 哪个电子货币兑换平台比较可靠?
- webmoney哪里买?
- fit测试主要目的?
- ozon支付方式怎么设置?
webmoney兑换,用爬虫技术能做到哪些有趣的事情?
看到这个问题必须来怒答一波~用python爬虫爬便宜机票了解一下?
喜欢旅行又怕吃土?让Python来爬取最便宜机票吧!图源:
videoblocks.com
你喜欢旅行吗?
这个问题通常会得到一个肯定的答案,随后引出一两个有关之前冒险经历的故事。大多数人都认为旅行是体验新文化和开阔视野的好方法。但如果问题是“你喜欢搜索机票的过程吗?”也许话题就到此为止了……
可事实上,便宜的机票往往也很重要!本文将尝试构建一个网络爬虫,该爬虫对特定目的地运行并执行带有浮动日期(首选日期前后最多三天)的航班价格搜索。它会将结果保存为excel文件并发送一封包含快速统计信息的电子邮件。显然,这个爬虫的目的就是帮助我们找到最优惠的价格!
你可以在服务器上运行脚本(一个简单的Raspberry Pi就可以),每天运行一到两次。结果会以邮件形式发送,建议将excel文件存入Dropbox文件夹,以便随时随地查看。
因为爬虫以“浮动日期”进行搜索,所以它会搜索首选日期前后最多三天的航班信息。尽管该脚本一次仅运行一对目的地,但可以很容易地改写该爬虫使其每个循环运行多个目的地。最终甚至可能找到一些错误票价...那会很有意思!
另一个爬虫某种意义上来讲,网络爬取是互联网“工作”的核心。
也许你认为这是一个十分大胆的说法,但谷歌就是从拉里·佩奇用Java和Python构建的网络爬虫开始的。爬虫不断地爬取信息,整个互联网都在试图为所有问题提供最佳的可能答案。网络爬取有不计其数的应用程序,即使更喜欢数据科学中的其他分支,你仍需要一些爬取技巧以获得数据。
这里用到的一些技术来自于最近新的一本佳作《Python网络数据采集》,书中包含与网络爬取相关的所有内容,并提供了大量简例和实例。甚至有一个特别有意思的章节,讲述如何解决验证码检验的问题。
Python的拯救第一个挑战就是选择爬取信息的平台,本文选择了客涯(Kayak)。我们试过了Momondo, 天巡(Skyscanner), 亿客行(Expedia)和其它一些网站,但是这些网站上的验证码特别变态。
在那些“你是人类吗?”的验证中,尝试了多次选择交通灯、十字路口和自行车后,客涯似乎是最好的选择,尽管短时间内加载太多页面它会跳出安全检查。
我们设法让机器人每4到6个小时查询一次网站,结果一切正常。虽然说不定哪个部分偶尔会出点小问题,但是如果收到验证码,既可以手动解决问题后启动机器人,也可以等待几小时后的自动重启。
如果你是网络爬取新手,或者不知道为何有些网站花费很大力气阻止网络爬取,那么为构建爬虫写下第一行代码前,你一定要多加努力。
谷歌的“网络爬取规范”:
http://lmgtfy.com/?q=web+scraping+etiquette
系紧安全带...导入并打开Chrome浏览器标签页后,会定义一些循环中会用到的函数。这个架构的构思大概是这样的:
· 一个函数用于启动机器人程序,表明想要搜索的城市和日期。
· 这个函数获得首轮搜索结果,按“最佳”航班排序,然后点击“加载更多结果”。
· 另一个函数会爬取整个页面,并返回一个dataframe数据表。
· 随后重复步骤2和步骤3,得出按“价格”和“航行时间”排序的结果。
· 发送一封简要总结价格(最低价和平均价)的邮件,并将带有这三种排序类型的dataframe数据表保存为一份excel文件。
· 以上所有步骤会在循环中重复,每X小时运行一次。
每个Selenium项目都以一个网页驱动器开始。我们使用Chromedriver驱动器,但还有其它选择。PhantomJS和Firefox也很受欢迎。下载Chromedriver后,将其置于一个文件夹中即可。第一行代码会打开一个空白Chrome标签页。
from time import sleep, strftime
from random import randint
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import smtplib
from email.mime.multipart import MIMEMultipart
# Change this to your own chromedriver path!
chromedriver_path = 'C:/{YOUR PATH HERE}/chromedriver_win32/chromedriver.exe'
driver = webdriver.Chrome(executable_path=chromedriver_path) # This will open the Chrome window
sleep(2)
这些是将用于整个项目的包。使用randint函数令机器人在每次搜索之间随机睡眠几秒钟。这对任何一个机器人来说都是必要属性。如果运行前面的代码,应该打开一个Chrome浏览器窗口,机器人会在其中导航。
一起来做一个快速测试:在另一个窗口上访问客涯网(http://kayak.com),选择往返城市和日期。选择日期时,确保选择的是“+/uploads/title/20231205/656e7e8b7a9e8.jpg3天”。由于在编写代码时考虑到了结果页面,所以如果只想搜索特定日期,很可能需要做一些微小的调整。
点击搜索按钮在地址栏获取链接。它应该类似于下面所使用的链接,将变量kayak定义为url,并从网页驱动器执行get方法,搜索结果就会出现。
无论何时,只要在几分钟内使用get命令超过两到三次,就会出现验证码。实际上可以自己解决验证码,并在下一次验证出现时继续进行想要的测试。从测试来看,第一次搜索似乎一直没有问题,所以如果想运行这份代码,并让它在较长的时间间隔后运行,必须解决这个难题。你并不需要十分钟就更新一次这些价格,对吧?
每个XPath都有陷阱到目前为止,已经打开了一个窗口,获取了一个网站。为了开始获取价格和其他信息,需要使用XPath或CSS选择器,我们选择了XPath。使用XPath导航网页可能会令人感到困惑,即使使用从inspector视图中直接使用“复制XPath”,但这不是获得所需元素的最佳方法。有时通过“复制XPath”这个方法获得的链接过于针对特定对象,以至于很快就失效了。《Python网络数据采集》一书很好地解释了使用XPath和CSS选择器导航的基础知识。
接下来,用Python选择最便宜的结果。上面代码中的红色文本是XPath选择器,在网页上任意一处右键单击选择“inspect”就可以看到它。在想要查看代码的位置,可以再次右键单击选择“inspect”。
为说明之前所观察到的从“inspector”复制路径的缺陷,请参考以下差异:
1 # This is what the copymethod would return. Right click highlighted rows on the right side and select “copy> Copy XPath”//*[@id=“wtKI/uploads/title/20231205/656e7e8b7a9e8.jpgprice_aTab”]/div[1]/div/div/div[1]/div/span/span
2 # This is what I used todefine the “Cheapest” buttoncheap_results= ‘//a[@data/uploads/title/20231205/656e7e8b7a9e8.jpgcode = “price”]’
第二种方法的简洁性清晰可见。它搜索具有data/uploads/title/20231205/656e7e8b7a9e8.jpgcode等于price属性的元素a。第一种方法查找id等于wtKI/uploads/title/20231205/656e7e8b7a9e8.jpgprice_aTab的元素,并遵循第一个div元素和另外四个div和两个span。这次……会成功的。现在就可以告诉你,id元素会在下次加载页面时更改。每次页面一加载,字母wtKI会动态改变,所以只要页面重新加载,代码就会失效。花些时间阅读XPath,保证你会有收获。
不过,使用复制的方法在不那么“复杂”的网站上工作,也是很好的!
基于以上所展示的内容,如果想在一个列表中以几个字符串的形式获得所有搜索结果该怎么办呢?其实很简单。每个结果都在一个对象中,这个对象的类是“resultWrapper”。获取所有结果可以通过像下面这样的for循环语句来实现。如果你能理解这一部分,应该可以理解接下来的大部分代码。它基本上指向想要的结果(结果包装器),使用某种方式(XPath)获得文本,并将其放置在可读对象中(首先使用flight_containers,然后使用flight_list)。
前三行已展示在图中,并且可以清楚地看到所需的内容,但是有获得信息的更优选择,需要逐一爬取每个元素。
准备起飞吧!最容易编写的函数就是加载更多结果的函数,所以代码由此开始。为了在不触发安全验证的前提下最大化所获取的航班数量,每次页面显示后,单击“加载更多结果”。唯一的新内容就是所添加的try语句,因为有时按钮加载会出错。如果它对你也有用,只需在前面展示的start_kayak函数中进行简要注释。
# Load more results to maximize the scraping
def load_more():
try:
more_results = '//a[@class = “moreButton”]'
driver.find_element_by_xpath(more_results).click()
# Printing these notes during the program helps me quickly check what it is doing
print('sleeping…..')
sleep(randint(45,60))
except:
pass
现在,经过这么长的介绍,已经准备好定义实际爬取页面的函数。
我们编译了下一个函数page_scrape中的大部分元素。有时这些元素会返回列表插入去程信息和返程信息之间。这里使用了一个简单的办法分开它们,比如在第一个 section_a_list和section_b_list变量中,该函数还返回一个flight_df数据表。所以可以分离在不同分类下得到的结果,之后再把它们合并起来。
def page_scrape():
“““This function takes care of the scraping part”““
xp_sections = '//*[@class=“section duration”]'
sections = driver.find_elements_by_xpath(xp_sections)
sections_list = [value.text for value in sections]
section_a_list = sections_list[::2] # This is to separate the two flights
section_b_list = sections_list[1::2] # This is to separate the two flights
# if you run into a reCaptcha, you might want to do something about it
# you will know there's a problem if the lists above are empty
# this if statement lets you exit the bot or do something else
# you can add a sleep here, to let you solve the captcha and continue scraping
# i'm using a SystemExit because i want to test everything from the start
if section_a_list == []:
raise SystemExit
# I'll use the letter A for the outbound flight and B for the inbound
a_duration = []
a_section_names = []
for n in section_a_list:
# Separate the time from the cities
a_section_names.append(''.join(n.split()[2:5]))
a_duration.append(''.join(n.split()[0:2]))
b_duration = []
b_section_names = []
for n in section_b_list:
# Separate the time from the cities
b_section_names.append(''.join(n.split()[2:5]))
b_duration.append(''.join(n.split()[0:2]))
xp_dates = '//div[@class=“section date”]'
dates = driver.find_elements_by_xpath(xp_dates)
dates_list = [value.text for value in dates]
a_date_list = dates_list[::2]
b_date_list = dates_list[1::2]
# Separating the weekday from the day
a_day = [value.split()[0] for value in a_date_list]
a_weekday = [value.split()[1] for value in a_date_list]
b_day = [value.split()[0] for value in b_date_list]
b_weekday = [value.split()[1] for value in b_date_list]
# getting the prices
xp_prices = '//a[@class=“booking/uploads/title/20231205/656e7e8b7a9e8.jpglink”]/span[@class=“price option/uploads/title/20231205/656e7e8b7a9e8.jpgtext”]'
prices = driver.find_elements_by_xpath(xp_prices)
prices_list = [price.text.replace('$','') for price in prices if price.text != '']
prices_list = list(map(int, prices_list))
# the stops are a big list with one leg on the even index and second leg on odd index
xp_stops = '//div[@class=“section stops”]/div[1]'
stops = driver.find_elements_by_xpath(xp_stops)
stops_list = [stop.text[0].replace('n','0') for stop in stops]
a_stop_list = stops_list[::2]
b_stop_list = stops_list[1::2]
xp_stops_cities = '//div[@class=“section stops”]/div[2]'
stops_cities = driver.find_elements_by_xpath(xp_stops_cities)
stops_cities_list = [stop.text for stop in stops_cities]
a_stop_name_list = stops_cities_list[::2]
b_stop_name_list = stops_cities_list[1::2]
# this part gets me the airline company and the departure and arrival times, for both legs
xp_schedule = '//div[@class=“section times”]'
schedules = driver.find_elements_by_xpath(xp_schedule)
hours_list = []
carrier_list = []
for schedule in schedules:
hours_list.append(schedule.text.split('\n')[0])
carrier_list.append(schedule.text.split('\n')[1])
# split the hours and carriers, between a and b legs
a_hours = hours_list[::2]
a_carrier = carrier_list[1::2]
b_hours = hours_list[::2]
b_carrier = carrier_list[1::2]
cols = (['Out Day', 'Out Time', 'Out Weekday', 'Out Airline', 'Out Cities', 'Out Duration', 'Out Stops', 'Out Stop Cities',
'Return Day', 'Return Time', 'Return Weekday', 'Return Airline', 'Return Cities', 'Return Duration', 'Return Stops', 'Return Stop Cities',
'Price'])
flights_df = pd.DataFrame({'Out Day': a_day,
'Out Weekday': a_weekday,
'Out Duration': a_duration,
'Out Cities': a_section_names,
'Return Day': b_day,
'Return Weekday': b_weekday,
'Return Duration': b_duration,
'Return Cities': b_section_names,
'Out Stops': a_stop_list,
'Out Stop Cities': a_stop_name_list,
'Return Stops': b_stop_list,
'Return Stop Cities': b_stop_name_list,
'Out Time': a_hours,
'Out Airline': a_carrier,
'Return Time': b_hours,
'Return Airline': b_carrier,
'Price': prices_list})[cols]
flights_df['timestamp'] = strftime(“%Y%m%d/uploads/title/20231205/656e7e8b7a9e8.jpg%H%M”) # so we can know when it was scraped
return flights_df
尽量让这些名字容易理解。记住变量a表示旅行的去程信息,变量b表示旅行的返程信息。接下来说说下一个函数。
等等,还有什么吗?截至目前,已经有了一个能加载更多结果的函数和一个能爬取其他结果的函数。本可以在此结束这篇文章,而你可以自行手动使用这些函数,并在浏览的页面上使用爬取功能。但是前文提到给自己发送邮件和一些其他信息的内容,这都包含在接下来的函数start_kayak中。
它要求填入城市名和日期,并由此打开一个kayak字符串中的地址,该字符串直接跳转到“最佳”航班结果排序页面。第一次爬取后,可以获取价格的顶部矩阵,这个矩阵将用于计算平均值和最小值,之后和客涯(Kayak)的预测结果(页面左上角)一同发送到邮件中。这是单一日期搜索时可能导致错误的原因之一,因其不包含矩阵元素。
def start_kayak(city_from, city_to, date_start, date_end):
“““City codes /uploads/title/20231205/656e7e8b7a9e8.jpg it's the IATA codes!
Date format /uploads/title/20231205/656e7e8b7a9e8.jpg YYYY/uploads/title/20231205/656e7e8b7a9e8.jpgMM/uploads/title/20231205/656e7e8b7a9e8.jpgDD”““
kayak = ('https://www.kayak.com/flights/' + city_from + '/uploads/title/20231205/656e7e8b7a9e8.jpg' + city_to +
'/' + date_start + '/uploads/title/20231205/656e7e8b7a9e8.jpgflexible/' + date_end + '/uploads/title/20231205/656e7e8b7a9e8.jpgflexible?sort=bestflight_a')
driver.get(kayak)
sleep(randint(8,10))
# sometimes a popup shows up, so we can use a try statement to check it and close
try:
xp_popup_close = '//button[contains(@id,”dialog/uploads/title/20231205/656e7e8b7a9e8.jpgclose”) and contains(@class,”Button/uploads/title/20231205/656e7e8b7a9e8.jpgNo/uploads/title/20231205/656e7e8b7a9e8.jpgStandard/uploads/title/20231205/656e7e8b7a9e8.jpgStyle close “)]'
driver.find_elements_by_xpath(xp_popup_close)[5].click()
except Exception as e:
pass
sleep(randint(60,95))
print('loading more.....')
# load_more()
print('starting first scrape.....')
df_flights_best = page_scrape()
df_flights_best['sort'] = 'best'
sleep(randint(60,80))
# Let's also get the lowest prices from the matrix on top
matrix = driver.find_elements_by_xpath('//*[contains(@id,”FlexMatrixCell”)]')
matrix_prices = [price.text.replace('$','') for price in matrix]
matrix_prices = list(map(int, matrix_prices))
matrix_min = min(matrix_prices)
matrix_avg = sum(matrix_prices)/len(matrix_prices)
print('switching to cheapest results…..')
cheap_results = '//a[@data/uploads/title/20231205/656e7e8b7a9e8.jpgcode = “price”]'
driver.find_element_by_xpath(cheap_results).click()
sleep(randint(60,90))
print('loading more…..')
# load_more()
print('starting second scrape…..')
df_flights_cheap = page_scrape()
df_flights_cheap['sort'] = 'cheap'
sleep(randint(60,80))
print('switching to quickest results…..')
quick_results = '//a[@data/uploads/title/20231205/656e7e8b7a9e8.jpgcode = “duration”]'
driver.find_element_by_xpath(quick_results).click()
sleep(randint(60,90))
print('loading more…..')
# load_more()
print('starting third scrape…..')
df_flights_fast = page_scrape()
df_flights_fast['sort'] = 'fast'
sleep(randint(60,80))
# saving a new dataframe as an excel file. the name is custom made to your cities and dates
final_df = df_flights_cheap.append(df_flights_best).append(df_flights_fast)
final_df.to_excel('search_backups//{}_flights_{}/uploads/title/20231205/656e7e8b7a9e8.jpg{}_from_{}_to_{}.xlsx'.format(strftime(“%Y%m%d/uploads/title/20231205/656e7e8b7a9e8.jpg%H%M”),
city_from, city_to,
date_start, date_end), index=False)
print('saved df…..')
# We can keep track of what they predict and how it actually turns out!
xp_loading = '//div[contains(@id,”advice”)]'
loading = driver.find_element_by_xpath(xp_loading).text
xp_prediction = '//span[@class=“info/uploads/title/20231205/656e7e8b7a9e8.jpgtext”]'
prediction = driver.find_element_by_xpath(xp_prediction).text
print(loading+'\n'+prediction)
# sometimes we get this string in the loading variable, which will conflict with the email we send later
# just change it to “Not Sure” if it happens
weird = '¯\\_(ツ)_/¯'
if loading == weird:
loading = 'Not sure'
username = 'YOUREMAIL@hotmail.com'
password = 'YOUR PASSWORD'
server = smtplib.SMTP('smtp.outlook.com', 587)
server.ehlo()
server.starttls()
server.login(username, password)
msg = ('Subject: Flight Scraper\n\n\
Cheapest Flight: {}\nAverage Price: {}\n\nRecommendation: {}\n\nEnd of message'.format(matrix_min, matrix_avg, (loading+'\n'+prediction)))
message = MIMEMultipart()
message['From'] = 'YOUREMAIL@hotmail.com'
message['to'] = 'YOUROTHEREMAIL@domain.com'
server.sendmail('YOUREMAIL@hotmail.com', 'YOUROTHEREMAIL@domain.com', msg)
print('sent email…..')
虽然没有使用Gmail账户测试发送邮件,但是可以搜索到很多的替代方法,前文提到的那本书中也有其他方法来实现这一点。如果已有一个Hotmail账户,只要替换掉个人的详细信息,它就会开始工作了。
如果想探索脚本的某一部分正在做什么,可以将脚本复制下来并在函数外使用它。这是彻底理解它的唯一方法。
利用刚才创造的一切在这些步骤之后,还可以想出一个简单的循环来使用刚创造的函数,同时使其持续运行。完成四个“花式”提示,写下城市和日期(输入)。因为测试时不想每次都输入这些变量,需要的时候可以使用以下这个清楚的方式进行替换。
如果已经做到了这一步,恭喜你!改进还有很多,比如与Twilio集成,发送文本消息而不是邮件。也可以使用VPN或更加难懂的方式同时从多个服务器上研究搜索结果。还有就是验证码的问题,验证码会时不时地跳出来,但对此类问题还是有解决办法的。不过,能走到这里已经是有很牢固的基础了,你可以尝试添加一些额外的要素。
使用脚本运行测试的示例
留言 点赞 关注
我们一起分享AI学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”
俄罗斯的互联网行业发展如何?
谢谢邀请。战斗民族俄罗斯充满着广袤、神秘、伟大、瑰丽的色彩;俄式建筑浑圆饱满、富丽堂皇;俄罗斯的美女金发碧眼、丰乳肥臀、挺拔俊美,在全世界有很高的声誉,美国人形容的天堂生活是:领美国工资,娶俄罗斯美女,住英国房子,吃中国饮食。连俄罗斯人自己也笑言美女是本国特产。
那么俄罗斯的互联网行业发展怎样呢?
我们来看一下:
1、起步跟中国差不多时间,目前发展跟同样迅速。
① 俄罗斯互联网始于1994年4月7日,比中国正式接入互联网还要早两个星期。中国是1994年4月20日,通过一条64K的国际专线接入国际互联网,从而开启了中国的互联网时代;而同年的4月7日,以俄罗斯国家域名.RU的正式注册,标志着战斗民族的互联网正式诞生。
② 俄罗斯目前互联网、电子商务高速发展
目前俄罗斯互联网普及率达66%,近年智能手机和移动互联网的普及,带来大量的手机上网用户,俄罗斯现有互联网用户总数近1亿,且每年以7.4%的速度增长。半数以上用户年龄在35岁以下。
俄电子通讯技术协会与俄高等经济学院联合发布的《俄罗斯互联网/uploads/title/20231205/656e7e8b7a9e8.jpg2013》报告指出,该年俄互联网产业规模已达上万亿卢布,加上互联网周边产业,其总规模可达5.2万亿卢布,占俄罗斯GDP8.5%左右。报告预测,至2018年俄罗斯互联网产业年均增长幅度可达15/uploads/title/20231205/656e7e8b7a9e8.jpg20%。
根据俄罗斯电子通讯联合会和国家科研机构"高等经济学院"共同推出的《2014/uploads/title/20231205/656e7e8b7a9e8.jpg2015年俄罗斯互联网经济调查研究报告》,2014年俄罗斯传统互联网市场规模为10940亿卢布,电子支付市场规模为4760亿卢布,互联网经济已达年GDP总量的2.2%。互联网相关市场的容量为118000亿卢布,约等于GDP总量的16%。
根据2015年俄罗斯InSales.ru公司提交的分析数据显示,2014年俄罗斯电子商务规模达6120亿卢布,比上年增长了31%。虽然2015/uploads/title/20231205/656e7e8b7a9e8.jpg2016年俄罗斯经济面临严重下滑,但专家预测,至2018年俄罗斯电子商务依然能够保持年均8/uploads/title/20231205/656e7e8b7a9e8.jpg10%的增幅。
2、俄罗斯有哪些大型互联网公司?
在包括社交、搜索、电商等领域,俄罗斯都有着不错的互联网公司。
① Yandex
Yandex公司,成立于1993年,比中国的BAT三巨头都要早,比国内新浪、网易、搜狐三大门户网站也早5年时间。
Yandex目前所提供的服务包括搜索、最新新闻、地图和百科、电子信箱、电子商务、互联网广告及其他服务。Yandex在俄罗斯本地搜索引擎的市场份额占到65%,远超俄罗斯Google22%。2011年5月在美国纳斯达克上市,员工规模达到5600多人,市值80亿美元。
②VK(原VKontakte)
VK.com 创立于2006年,是俄罗斯最大的社交网站,发展至今已经不仅仅是大学生和高校毕业生的联系网,它已经成长为仅次于搜索引擎Yandex的俄罗斯第二大网站。根据eBizMBA排名,为世界第八大受欢迎的社交网站,2014年被http://Mail.ru收购。
③ Mail.ru
Mail.ru是俄罗斯本土最大的门户网站,在《福布斯》2月发布的俄罗斯互联网公司价值排名中,Mail.ru以47亿美元首次超过Yandex成为俄罗斯市值最高的互联网公司。腾讯是其的股东之一,拥有7.8%的集团股份。
④ Ozon.ru
Ozon.ru是俄罗斯最大的在线零售商。于1998年由俄公司Reksoft成立。它主要商品是书籍,电子产品,音乐和电影。公司主要股东包括:Baring Vostock、Index Ventures、ru/uploads/title/20231205/656e7e8b7a9e8.jpgNet、乐天、英特尔投资、Holzbrink和思科。2005年,Ozon销售额大约为1930万美元。2007年4月,Ozon.ru从霍尔茨布林克风险投资公司,Index Ventures和Baring Vostok募集到1800万美元风险投资资金。2011年9月,Ozon将1亿美元投资到其4个主要业务:在线零售商Ozon.ru;船舶公司O/uploads/title/20231205/656e7e8b7a9e8.jpgcourier;Ozon;旅游、鞋和配件零售商 Sapato.ru。2012年,公司拥有1450万注册用户和2.5亿美元收入,并聘请1700名员工。
⑤ Яндекс
Яндекс创立于2000年,Yandex公司的子公司,公司市值43亿美金,该平台共有18000家商家入驻。Yandex积极在其它业务领域布局来扩大其非广告领域营收占比。公司收购俄罗斯最大的汽车门户网站Auto.ru后,涉足出租车服务业务,将"Яндекс.Такси"单拆出来成立为子公司,砍掉了"Яндекс.Мастер"项目,从物流服务商Multiship手中购买并成立自己的电商配送平台"Яндекс.Доставку",该平台为电商商家提供发货服务。
⑥ Avito
Avito是一家创立于2007年的全球第三大分类信息发布平台。这家公司目前占有俄罗斯分类信息市场25%的流量与15%的产值。2012年,Avito产生了约3000万美元的销售收入。随着Slando与OLX的并入,Avito有望依靠更加庞大与稳定的用户群获得营收,因此广告将成为未来收入的重要组成部分。目前,Avito拥有14万日活跃用户,但其中有4万用户以前从不使用这项服务。
以上是俄罗斯本土比较著名的互联网公司。而中美的互联网公司在俄罗斯也有着不错的市场,比如google、Facebook、eBay、Aliexpress(阿里速卖通)等等。
如何实现一键调用以太坊智能合约?
有人问如何用按钮调用智能合约,在这篇文章中,我将给出一个简单但有希望有效的演示,说明JavaScript开发人员如何创建一个能够调用智能合约的网页,并通过单击即可向其汇款(以太币)。
要做到这一点,用户将需要使用支持Web3的浏览器,因此可以使用像Parity或Mist这样的可安装浏览器,或者他们可以使用像MetaMask这样的浏览器扩展。
此外,虽然我可以教你如何直接使用web3API,但我将教你如何使用一个新的很方便的库与以太网智能合约EthJS交互。
你的网站需要等待ready事件,然后检查全局web3对象。看起来像这样:
window.addEventListener('load', function() {
// Check if Web3 has been injected by the browser:
if (typeof web3 !== 'undefined') {
// You have a web3 browser! Continue below!
startApp(web3);
} else {
// Warn the user that they need to get a web3 browser
// Or install MetaMask, maybe with a nice graphic.
})
从MetaMask开发人员指南中复制。
在这个例子中,我假设你正在使用像Browserify或Webpack这样的JavaScript捆绑器,并且知道如何从NPM安装模块。
在你的应用程序设置中,你将使用一些不同的ethjs模块,你将使用全局web3对象的currentProvider属性初始化它们,这就是它与区块链的对话方式。
const Eth = require('ethjs/uploads/title/20231205/656e7e8b7a9e8.jpgquery')
const EthContract = require('ethjs/uploads/title/20231205/656e7e8b7a9e8.jpgcontract')
function startApp(web3) {
const eth = new Eth(web3.currentProvider)
const contract = new EthContract(eth)
initCon
一旦实例化了合约,就可以使用它来创建对网络上实时合约的引用。要做到这一点,你需要两件事:
合约地址。
合约ABI。
ABI是应用程序二进制接口,它告诉你的JavaScript如何与智能合约通信。它只是描述合约方法的JSON数据。
通常,如果你发布合约,你知道如何获得ABI,如果你要与其他人签订合约,他们应该提供ABI,尽管有时候你可以找到与Etherscan等区块浏览器的合约相匹配的ABI。
假设你有可用的ABI和地址,并了解我们现在如何创建合约对象。在这个例子中,我将使用仅包含Token标准中的transfer(to,value)方法的ABI:
const abi = [{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
}]
const address = '0xdeadbeef123456789000000000000'
function initContract (contract) {
const MiniToken = contract(abi)
const miniToken = MiniToken.at(address)
listenForClicks(miniToken)
}
现在我们已经为智能合约初始化了一个JavaScript接口,所以我们只需要创建一个小HTML。
还有一点JavaScript来响应点击,并发送这些资金:
function listenForClicks (miniToken) {
var button = document.querySelector('button.transferFunds')
button.addEventListener('click', function() {
miniToken.transfer(toAddress, value, { from: addr })
.then(function (txHash) {
console.log('Transaction sent')
console.dir(txHash)
waitForTxToBeMined(txHash)
})
.catch(console.error)
})
}
请注意,如果此交易也发送以太,你将向包含from字段的选项哈希添加value:'10000'。该值以wei为单位,为1x10 ^ /uploads/title/20231205/656e7e8b7a9e8.jpg18以太。转换的简单方法是这样的:
var inWei = web3.toWei('10', 'ether')
对于普通的Web开发人员来说,一个奇怪的部分是交易响应并不意味着交易现在已经完成,它只是意味着它已经被传输到网络。它仍然需要被挖掘,而在以太坊中,平均需要大约14秒(阻断时间,查看EthStats.net上的统计数据)。
现在还没有很好的订阅方法可以等待挖掘交易,所以你需要用收到的txHash进行轮询。是的,这很乏味,所以我会告诉你如何使用新的JavaScript async/await模式来减少痛苦:
async function waitForTxToBeMined (txHash) {
let txReceipt
while (!txReceipt) {
try {
txReceipt = await eth.getTransactionReceipt(txHash)
} catch (err) {
return indicateFailure(err)
}
}
indicateSuccess()
}
如此而已!
哪个电子货币兑换平台比较可靠?
这家平台应该是比较安全的,不过还是慎重点好。黄金电子货币兑换中心是专门从事于在线电子货币兑换,充值和提现等服务的网站,专业为国内外用户提供Perfectmoney(PM),Webmoney(WMZ), Payeer(PR), OKPay, BTC/uploads/title/20231205/656e7e8b7a9e8.jpgE USD, AdvCash等国际主流电子货币双向兑换。
现在国内外的一些平台也已经上线了比特币、瑞泰币、莱特币、活力币等数字加密货币。
webmoney哪里买?
Webmoney是日本最流行的网上货币
几乎适用于所有的日本游戏支付及网上购物
支持的日服游戏:
天翼之链 惊天动地(CABAL) 神魔传说(RAPPELZ) MOE 挑战(DEKARON)红石(REDSTONE) 冒险岛 ECO 英雄 M2机甲演义 巨商传 行会战争(GUILDWAR) 希望 石器时代2 宿命传说 热血江湖 破天一剑 地下城与勇者 轩辕传飞天历险 梦幻之星OL/uploads/title/20231205/656e7e8b7a9e8.jpgBB 最终幻想FF11 墨香 洛奇 冒险岛 仙境传说 LAPIS 天堂1 天堂2 LOST ROHAN 卓越之剑 等。
网上购物:
可以和日本最大的商业购物网站乐天市场http://www.rakuten.co.jp/进行自由点数互转,自由享受网上购物的乐趣
乐天市场:日本最大的网上商店街,汇集了10000家以上的网上商店,商品500万种以上。在乐天的共同购物里面可以买到很便宜的日常生活用品。
fit测试主要目的?
在软件开发的生命周期中,每个人都对质量负有责任。理想情况下,开发人员在开发周期中,用像 Junit 和 TestNG 这样的测试工具保证早期质量,而质量保证团队用功能性系统测试在周期末端跟进,使用像 Selenium 这样的工具。但是即使拥有优秀的质量保证,有些应用程序在交付的时候仍然被认为是质量低下的。为什么呢?因为它们并没有做它们应当做的事。
在客户、(编写应用程序需求的)业务部门和(实现需求的)开发团队之间的沟通错误,通常是摩擦的原因,有时还是开发项目彻底失败的常见原因。幸运的是,存在一些方法可以帮助需求作者和实现者之间尽早 沟通。
FIT 化的解决方案
集成测试框架 (FIT)是一个测试平台,可以帮助需求编写人员和把需求变成可执行代码的人员之间的沟通。使用 FIT,需求被做成表格模型,充当开发人员编写的测试的数据模型。表格本身充当输入和测试的预期输出。
下载 FIT
集成测试框架(FIT)最初是由 Ward Cunningham 创建的,他就是 wiki 的发明人。请访问 Cunningham 的 Web 站点了解关于 FIT 的更多知识并 免费下载它。
图 1 显示了用 FIT 创建的结构化模型。第一行是测试名称,下一行的三列是与输入(value1 和 value2)和预期结果(trend())有关的标题。
图 1. 用 FIT 创建的结构化模型
好消息是,对于编程没有经验的人也能编写这个表格。FIT 的设计目的就是让消费者或业务团队在开发周期中,尽早与实现他们想法的开发人员协作。创建应用程序需求的简单表格式模型,可以让每个人清楚地看出代码和需求是否是一致的。
清单 1 是与图 1 的数据模型对应的 FIT 代码。不要太多地担心细节 —— 只要注意代码有多么简单,而且代码中没有包含验证逻辑(例如,断言等)。可能还会注意到一些与表 1 中的内容匹配的变量和方法名称;关于这方面的内容后面介绍。
清单 1. 根据 FIT 模型编写的代码
package test.com.acme.fit.impl;
import com.acme.sedlp.trend.Trender;
import fit.ColumnFixture;
public class TrendIndicatorextends ColumnFixture {
public double value1;
public double value2;
public String trend(){
return Trender.determineTrend(value1, value2).getName();
}
}
清单 1 中的代码由研究上面表格并插入适当代码的开发人员编写。最后,把所有东西合在一起,FIT 框架读取表 1 的数据,调用对应的代码,并确定结果。
FIT 和 JUnit
FIT 的优美之处在于,它让组织的消费者或业务端能够尽早参与测试过程(例如,在开发期间)。JUnit 的力量在于编码过程中的单元测试,而 FIT 是更高层次的测试工具,用来判断规划的需求实现的正确性。
例如,虽然 JUnit 擅长验证两个 Money 对象的合计与它们的两个值的合计相同,但 FIT 可以验证总的订单价格是其中商品的价格减去任何相关折扣之后的合计。区别虽然细微,但的确重大!在 JUnit 示例中,要处理具体的对象(或者需求的实现),但是使用 FIT 时要处理的是高级的业务过程。
这很有意义,因为编写需求的人通常不太考虑 Money 对象 —— 实际上,他们可能根本不知道这类东西的存在!但是,他们确实要考虑,当商品被添加到订单时,总的订单价格应当是商品的价格减去所有折扣。
FIT 和 JUnit 之间绝不是竞争关系,它们是保证代码质量的好搭档,正如在后面的 案例研究中将要看到的。
测试用的 FIT 表格
表格是 FIT 的核心。有几种不同类型的表格(用于不同的业务场景),FIT 用户可以用不同的格式编写表格。用 HTML 编写表格甚至用 Microsoft Excel 编写都是可以的,如图 2 所示:
图 2. 用 Microsoft Excel 编写的表格
也有可能用 Microsoft Word 这样的工具编写表格,然后用 HTML 格式保存,如图 3 所示:
图 3. 用 Microsoft Word 编写的表格
开发人员编写的用来执行表格数据的代码叫作装备(fixture)。要创建一个装备类型,必须扩展对应的 FIT 装备,它映射到对应的表。如前所述,不同类型的表映射到不同的业务场景。
用装备进行装配
最简单的表和装备组合,也是 FIT 中最常用的,是一个简单的列表格,其中的列映射到预期过程的输入和输出。对应的装备类型是 ColumnFixture。
如果再次查看 清单 1,将注意到 TrendIndicator 类扩展了 ColumnFixture,而且也与图 3 对应。请注意在图 3 中,第一行的名称匹配完全限定名称(test.com.acme.fit.impl.TrendIndicator)。下一行有三列。头两个单元格的值匹配 TrendIndicator 类的 public 实例成员(value1 和 value2),最后一个单元格的值只匹配 TrendIndicator 中的方法(trend)。
现在来看清单 1 中的 trend 方法。它返回一个 String 值。可以猜测得到,对于表中每个剩下的行,FIT 都会替换值并比较结果。在这个示例中,有三个 “数据” 行,所以 FIT 运行 TrendIndicator 装备三次。第一次,value1 被设置成 84.0,value2 设置成 71.2。然后 FIT 调用 trend 方法,并把从方法得到的值与表中的值比较,应当是 “decreasing”。
通过这种方式,FIT 用装备代码测试 Trender 类,每次 FIT 执行 trend 方法时,都执行类的 determineTrend 方法。当代码测试完成时,FIT 生成如图 4 所示的报告:
图 4. FIT 报告 trend 测试的结果
trend 列单元格的绿色表明测试通过(例如,FIT 设置 value1为 84.0,value2 为 71.2,调用 trend 得到返回值 “decreasing”)。
查看 FIT 运行
可以通过命令行,用 Ant 任务并通过 Maven 调用 FIT,从而简单地把 FIT 测试插入构建过程。因为自动进行 FIT 测试,就像 JUnit 测试一样,所以也可以定期运行它们,例如在持续集成系统中。
最简单的命令行运行器,如清单 2 所示,是 FIT 的 FolderRunner,它接受两个参数 —— 一个是 FIT 表格的位置,一个是结果写入的位置。不要忘记配置类路径!
清单 2. FIT 的命令行
%>java fit.runner.FolderRunner ./test/fit ./target/
FIT 通过插件,还可以很好地与 Maven 一起工作,如清单 3 所示。只要下载插件,运行 fit:fit命令,就 OK 了!(请参阅 参考资料 获得 Maven 插件。)
清单 3. Maven 得到 FIT
C:/dev/proj/edoa>maven fit:fit
__ __
| // |__ _Apache__ ___
| |//| / _` / V / /uploads/title/20231205/656e7e8b7a9e8.jpg_) ' / ~ intelligent projects ~
|_| |_/__,_|/_//___|_||_| v. 1.0.2
build:start:
java:prepare/uploads/title/20231205/656e7e8b7a9e8.jpgfilesystem:
java:compile:
[echo] Compiling to C:/dev/proj/edoa/target/classes
java:jar/uploads/title/20231205/656e7e8b7a9e8.jpgresources:
test:prepare/uploads/title/20231205/656e7e8b7a9e8.jpgfilesystem:
test:test/uploads/title/20231205/656e7e8b7a9e8.jpgresources:
test:compile:
fit:fit:
[java] 2 right, 0 wrong, 0 ignored, 0exceptions
BUILD SUCCESSFUL
Total time: 4 seconds
Finished at: Thu Feb 02 17:19:30 EST 2006
试用 FIT:案例研究
现在已经了解了 FIT 的基础知识,我们来做一个练习。如果还没有 下载 FIT,现在是下载它的时候了!如前所述,这个案例研究显示出可以容易地把 FIT 和 JUnit 测试组合在一起,形成多层质量保证。
假设现在要为一个酿酒厂构建一个订单处理系统。酿酒厂销售各种类型的酒类,但是它们可以组织成两大类:季节性的和全年性的。因为酿酒厂以批发方式运作,所以酒类销售都是按桶销售的。对于零售商来说,购买多桶酒的好处就是折扣,而具体的折扣根据购买的桶数和酒是季节性还是全年性的而不同。
麻烦的地方在于管理这些需求。例如,如果零售店购买了 50 桶季节性酒,就没有折扣;但是如果这 50 桶不是 季节性的,那么就有 12% 的折扣。如果零售店购买100 桶季节性酒,那就有折扣,但是只有 5%。100 桶更陈的非季节性酒的折扣达到 17%。购买量达到 200 时,也有类似的规矩。
对于开发人员,像这样的需求集可能让人摸不着头脑。但是请看,我们的啤酒/uploads/title/20231205/656e7e8b7a9e8.jpg酿造行业分析师用 FIT 表可以很容易地描述出这个需求,如图 5 所示:
图 5. 我的业务需求非常清晰:
表格语义
这个表格从业务的角度来说很有意义,它确实很好地规划出需求。但是作为开发人员,还需要对表格的语言了解更多一些,以便从表格得到值。首先,也是最重要的,表格中的初始行说明表格的名称,它恰好与一个匹配的类对应(org.acme.store.discount.DiscountStructureFIT)。命名要求表格作者和开发人员之间的一些协调。至少,需要指定完全限定的表格名称(也就是说,必须包含包名,因为 FIT 要动态地装入对应的类)。
请注意表格的名称以 FIT 结束。第一个倾向可能是用 Test结束它,但要是这么做,那么在自动环境中运行 FIT 测试和 JUnit 测试时,会与 JUnit 产生些冲突,JUnit 的类通常通过命名模式查找,所以最好避免用 Test 开始或结束 FIT 表格名称。
下一行包含五列。每个单元格中的字符串都特意用斜体格式,这是 FIT 的要求。前面学过,单元格名称与装备的实例成员和方法匹配。为了更简洁,FIT 假设任何值以括号结束的单元格是方法,任何值不以括号结束的单元格是实例成员。
特殊智能
FIT 在处理单元格的值,进行与对应装备类的匹配时,采用智能解析。如 图 5 所示,第二行单元格中的值是用普通的英文编写的,例如 “number of cases”。FIT 试图把这样的字符串按照首字母大写方式连接起来;例如,“number of cases” 变成 “numberOfCases”,然后 FIT 试图找到对应的装备类。这个原则也适用于方法 —— 如图 5 所示,“discount price()” 变成了 “discountPrice()”。
FIT 还会智能地猜测单元格中值的具体类型。例如,在 图 5 余下的八行中,每一列都有对应的类型,或者可以由 FIT 准确地猜出,或者要求一些定制编程。在这个示例中,图 5 有三种不同类型。与 “number of cases” 关联的列匹配到 int,而与 “is seasonal” 列关联的值则匹配成 boolean。
剩下的三列,“list price per case”、“discount price()” 和 “discount amount()” 显然代表当前值。这几列要求定制类型,我将把它叫作 Money。有了它之后,应用程序就要求一个代表钱的对象,所以在我的 FIT 装备中遵守少量语义就可以利用上这个对象!
FIT 语义总结
表 1 总结了命名单元格和对应的装备实例变量之间的关系:
表 1. 单元格到装备的关系:实例变量
单元格值对应的装备实例变量类型list price per caselistPricePerCaseMoneynumber of casesnumberOfCasesintis seasonalisSeasonalboolean
表 2 总结了 FIT 命名单元格和对应的装备方法之间的关系:
表 2. 单元格到装备的关系:方法
表格单元格的值对应的装备方法返回类型discount price()discountPriceMoneydiscount amount()discountAmountMoney
该构建了!
要为酿酒厂构建的订单处理系统有三个主要对象:一个 PricingEngine 处理包含折扣的业务规则,一个 WholeSaleOrder 代表订单,一个 Money 类型代表钱。
Money 类
第一个要编写的类是 Money类,它有进行加、乘和减的方法。可以用 JUnit 测试新创建的类,如清单 14 所示:
清单 4. JUnit 的 MoneyTest 类
package org.acme.store;
import junit.framework.TestCase;
public class MoneyTest extendsTestCase {
public void testToString()throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
assertEquals("$100.00", total.toString());
}
public void testEquals() throwsException{
Money money = Money.parse("$10.00");
Money control = new Money(10.00);
assertEquals(control, money);
}
public void testMultiply()throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
Money discountAmount = total.mpy(0.05);
assertEquals("$5.00", discountAmount.toString());
}
public void testSubtract()throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
Money discountAmount = total.mpy(0.05);
Money discountedPrice = total.sub(discountAmount);
assertEquals("$95.00", discountedPrice.toString());
}
}
WholeSaleOrder 类
然后,定义 WholeSaleOrder 类型。这个新对象是应用程序的核心:如果 WholeSaleOrder 类型配置了桶数、每桶价格和产品类型(季节性或全年性),就可以把它交给 PricingEngine,由后者确定对应的折扣并相应地在 WholeSaleOrder 实例中配置它。
WholesaleOrder 类的定义如清单 5 所示:
清单 5. WholesaleOrder 类
package org.acme.store.discount.engine;
import org.acme.store.Money;
public class WholesaleOrder {
private int numberOfCases;
private ProductType productType;
private Money pricePerCase;
private double discount;
public double getDiscount() {
return discount;
}
public void setDiscount(doublediscount) {
this.discount = discount;
}
public Money getCalculatedPrice() {
Money totalPrice = this.pricePerCase.mpy(this.numberOfCases);
Money tmpPrice = totalPrice.mpy(this.discount);
return totalPrice.sub(tmpPrice);
}
public Money getDiscountedDifference() {
Money totalPrice = this.pricePerCase.mpy(this.numberOfCases);
returntotalPrice.sub(this.getCalculatedPrice());
}
public int getNumberOfCases() {
return numberOfCases;
}
public void setNumberOfCases(intnumberOfCases) {
this.numberOfCases = numberOfCases;
}
public voidsetProductType(ProductType productType) {
this.productType = productType;
}
public String getProductType() {
return productType.getName();
}
public voidsetPricePerCase(Money pricePerCase) {
this.pricePerCase = pricePerCase;
}
public Money getPricePerCase() {
return pricePerCase;
}
}
从清单 5 中可以看到,一旦在 WholeSaleOrder 实例中设置了折扣,就可以通过分别调用 getCalculatedPrice 和 getDiscountedDifference 方法得到折扣价格和节省的钱。
更好地测试这些方法(用 JUnit)!
定义了 Money 和 WholesaleOrder 类之后,还要编写 JUnit 测试来验证 getCalculatedPrice 和 getDiscountedDifference 方法的功能。测试如清单 6 所示:
清单 6. JUnit 的 WholesaleOrderTest 类
packageorg.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
importorg.acme.store.discount.engine.WholesaleOrder;
public class WholesaleOrderTestextends TestCase {
/*
* Test method for 'WholesaleOrder.getCalculatedPrice()'
*/
public void testGetCalculatedPrice() {
WholesaleOrder order = newWholesaleOrder();
order.setDiscount(0.05);
order.setNumberOfCases(10);
order.setPricePerCase(new Money(10.00));
assertEquals("$95.00", order.getCalculatedPrice().toString());
}
/*
* Test method for 'WholesaleOrder.getDiscountedDifference()'
*/
public void testGetDiscountedDifference() {
WholesaleOrder order = newWholesaleOrder();
order.setDiscount(0.05);
order.setNumberOfCases(10);
order.setPricePerCase(new Money(10.00));
assertEquals("$5.00", order.getDiscountedDifference().toString());
}
}
PricingEngine 类
PricingEngine 类利用业务规则引擎,在这个示例中,是 Drools(请参阅 “关于 Drools”)。PricingEngine 极为简单,只有一个 public 方法:applyDiscount。只要传递进一个 WholeSaleOrder 实例,引擎就会要求 Drools 应用折扣,如清单 7 所示:
清单 7. PricingEngine 类
package org.acme.store.discount.engine;
import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.drools.io.RuleBaseLoader;
public class PricingEngine {
private static final String RULES="BusinessRules.drl";
private static RuleBase businessRules;
private static void loadRules()throws Exception{
if (businessRules==null){
businessRules = RuleBaseLoader.
loadFromUrl(PricingEngine.class.getResource(RULES));
}
}
public static voidapplyDiscount(WholesaleOrder order) throws Exception{
loadRules();
WorkingMemory workingMemory = businessRules.newWorkingMemory( );
workingMemory.assertObject(order);
workingMemory.fireAllRules();
}
}
关于 Drools
Drools 是一个为 Java™ 语言度身定制的规则引擎实现。它提供可插入的语言实现,目前规则可以用 Java、Python 和 Groovy 编写。要获得更多信息,或者下载 Drools,请参阅 Drools 主页。
Drools 的规则
必须在特定于 Drools 的 XML 文件中定义计算折扣的业务规则。例如,清单 8 中的代码段就是一个规则:如果桶数大于 9,小于 50,不是季节性产品,则订单有 5% 的折扣。
清单 8. BusinessRules.drl 文件的示例规则
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs="http://www.w3.org/2001/XMLSchema/uploads/title/20231205/656e7e8b7a9e8.jpginstance"
xs:schemaLocation="http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd">
WholesaleOrder
order.getNumberOfCases() > 9
order.getNumberOfCases() < 50
order.getProductType() == "year/uploads/title/20231205/656e7e8b7a9e8.jpground"
order.setDiscount(0.05);
标记团队测试
有了 PricingEngine 并定义了应用程序规则之后,可能渴望验证所有东西都工作正确。现在问题就变成,用 JUnit 还是 FIT?为什么不两者都用呢?通过 JUnit 测试所有组合是可能的,但是要进行许多编码。最好是用 JUnit 测试少数几个值,迅速地验证代码在工作,然后依靠 FIT 的力量运行想要的组合。请看看当我这么尝试时发生了什么,从清单 9 开始:
清单 9. JUnit 迅速地验证了代码在工作
packageorg.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
importorg.acme.store.discount.engine.PricingEngine;
importorg.acme.store.discount.engine.ProductType;
importorg.acme.store.discount.engine.WholesaleOrder;
public class DiscountEngineTestextends TestCase {
public void testCalculateDiscount() throwsException{
WholesaleOrder order = newWholesaleOrder();
order.setNumberOfCases(20);
order.setPricePerCase(new Money(10.00));
order.setProductType(ProductType.YEAR_ROUND);
PricingEngine.applyDiscount(order);
assertEquals(0.05, order.getDiscount(), 0.0);
}
public void testCalculateDiscountNone() throws Exception{
WholesaleOrder order = newWholesaleOrder();
order.setNumberOfCases(20);
order.setPricePerCase(new Money(10.00));
order.setProductType(ProductType.SEASONAL);
PricingEngine.applyDiscount(order);
assertEquals(0.0, order.getDiscount(), 0.0);
}
}
还没用 FIT?那就用 FIT!
在 图 5 的 FIT 表格中有八行数据值。可能已经在 清单 7 中编写了前两行的 JUnit 代码,但是真的想编写整个测试吗?编写全部八行的测试或者在客户添加新规则时再添加新的测试,需要巨大的耐心。好消息就是,现在有了更容易的方法。不过,不是忽略测试 —— 而是用 FIT!
FIT 对于测试业务规则或涉及组合值的内容来说非常漂亮。更好的是,其他人可以完成在表格中定义这些组合的工作。但是,在为表格创建 FIT 装备之前,需要给 Money 类添加一个特殊方法。因为需要在 FIT 表格中代表当前货币值(例如,像 $100.00 这样的值),需要一种方法让 FIT 能够认识 Money 的实例。做这件事需要两步:首先,必须把 static parse 方法添加到定制数据类型,如清单 10 所示:
清单 10. 添加 parse 方法到 Money 类
public static Money parse(String value){
return newMoney(Double.parseDouble(StringUtils.remove(value, '
Money 类的 parse 方法接受一个 String 值(例如,FIT 从表格中取出的值)并返回配置正确的 Money 实例。在这个示例中,$ 字符被删除,剩下的 String 被转变成 double,这与 Money 中现有的构造函数匹配。
不要忘记向 MoneyTest 类添加一些测试来来验证新添加的 parse 方法按预期要求工作。两个新测试如清单 11 所示:
清单 11. 测试 Money 类的 parse 方法
public void testParse() throwsException{
Money money = Money.parse("$10.00");
assertEquals("$10.00", money.toString());
}
public void testEquals() throwsException{
Money money = Money.parse("$10.00");
Money control = new Money(10.00);
assertEquals(control, money);
}
编写 FIT 装备
现在可以编写第一个 FIT 装备了。实例成员和方法已经在表 1 和表 2 中列出,所以只需要把事情串在一起,添加一两个方法来处理定制类型:Money。为了在装备中处理特定类型,还需要添加另一个 parse 方法。这个方法的签名与前一个略有不同:这个方法是个对 Fixture 类进行覆盖的实例方法,这个类是 ColumnFixture 的双亲。
请注意在清单 12 中,DiscountStructureFIT 的 parse方法如何比较 class 类型。如果存在匹配,就调用 Money 的定制 parse 方法;否则,就调用父类(Fixture)的 parse 版本。
清单 12 中剩下的代码是很简单的。对于图 5 所示的 FIT 表格中的每个数据行,都设置值并调用方法,然后 FIT 验证结果!例如,在 FIT 测试的第一次运行中,DiscountStructureFIT 的 listPricePerCase 被设为 $10.00,numberOfCases 设为 10,isSeasonal 为 true。然后执行 DiscountStructureFIT 的 discountPrice,返回的值与 $100.00 比较,然后执行 discountAmount,返回的值与 $0.00 比较。
清单 12. 用 FIT 进行的折扣测试
package org.acme.store.discount;
import org.acme.store.Money;
importorg.acme.store.discount.engine.PricingEngine;
importorg.acme.store.discount.engine.ProductType;
importorg.acme.store.discount.engine.WholesaleOrder;
import fit.ColumnFixture;
public class DiscountStructureFITextends ColumnFixture {
public Money listPricePerCase;
public int numberOfCases;
public boolean isSeasonal;
public Money discountPrice() throwsException {
WholesaleOrder order = this.doOrderCalculation();
return order.getCalculatedPrice();
}
public Money discountAmount() throwsException {
WholesaleOrder order = this.doOrderCalculation();
return order.getDiscountedDifference();
}
/**
* required by FIT for specific types
*/
public Object parse(String value, Classtype) throws Exception {
if (type == Money.class) {
return Money.parse(value);
} else {
return super.parse(value, type);
}
}
private WholesaleOrderdoOrderCalculation() throws Exception {
WholesaleOrder order = newWholesaleOrder();
order.setNumberOfCases(numberOfCases);
order.setPricePerCase(listPricePerCase);
if (isSeasonal) {
order.setProductType(ProductType.SEASONAL);
} else {
order.setProductType(ProductType.YEAR_ROUND);
}
PricingEngine.applyDiscount(order);
return order;
}
}
现在,比较 清单 9 的 JUnit 测试用例和清单 12。是不是清单 12 更有效率?当然可以 用 JUnit 编写所有必需的测试,但是 FIT 可以让工作容易得多!如果感觉到满意(应当是满意的!),可以运行构建,调用 FIT 运行器生成如图 6 所示的结果:
ozon支付方式怎么设置?
1. ozon支付方式可以通过进入ozon平台的设置页面,选择“支付设置”,进入支付方式的设置页面。
2. 在支付方式的设置页面中,可以设置自己的银行卡、信用卡等支付方式,同时还可以选择使用第三方支付机构,如支付宝、微信支付等。
3. 延伸内容:在进行支付方式的设置时,需要注意保护自己的账户安全,确认自己的支付方式不会被他人盗用。
同时,对于某些特殊购物的场景,例如大额支付,可以选择更加安全的支付方式,例如银行转账等。
本站涵盖的内容、图片等数据系网络收集,部分未能与原作者取得联系。若涉及版权问题,请联系ynstorm@foxmail.com进行删除!
- webmoney兑换,用爬虫技术能做到哪些有趣的事情?
- 俄罗斯的互联网行业发展如何?
- 如何实现一键调用以太坊智能合约?
- 哪个电子货币兑换平台比较可靠?
- webmoney哪里买?
- fit测试主要目的?
- ozon支付方式怎么设置?
webmoney兑换,用爬虫技术能做到哪些有趣的事情?
看到这个问题必须来怒答一波~用python爬虫爬便宜机票了解一下?
喜欢旅行又怕吃土?让Python来爬取最便宜机票吧!
图源:
videoblocks.com
你喜欢旅行吗?
这个问题通常会得到一个肯定的答案,随后引出一两个有关之前冒险经历的故事。大多数人都认为旅行是体验新文化和开阔视野的好方法。但如果问题是“你喜欢搜索机票的过程吗?”也许话题就到此为止了……
可事实上,便宜的机票往往也很重要!本文将尝试构建一个网络爬虫,该爬虫对特定目的地运行并执行带有浮动日期(首选日期前后最多三天)的航班价格搜索。它会将结果保存为excel文件并发送一封包含快速统计信息的电子邮件。显然,这个爬虫的目的就是帮助我们找到最优惠的价格!
你可以在服务器上运行脚本(一个简单的Raspberry Pi就可以),每天运行一到两次。结果会以邮件形式发送,建议将excel文件存入Dropbox文件夹,以便随时随地查看。
因为爬虫以“浮动日期”进行搜索,所以它会搜索首选日期前后最多三天的航班信息。尽管该脚本一次仅运行一对目的地,但可以很容易地改写该爬虫使其每个循环运行多个目的地。最终甚至可能找到一些错误票价...那会很有意思!
另一个爬虫
某种意义上来讲,网络爬取是互联网“工作”的核心。
也许你认为这是一个十分大胆的说法,但谷歌就是从拉里·佩奇用Java和Python构建的网络爬虫开始的。爬虫不断地爬取信息,整个互联网都在试图为所有问题提供最佳的可能答案。网络爬取有不计其数的应用程序,即使更喜欢数据科学中的其他分支,你仍需要一些爬取技巧以获得数据。
这里用到的一些技术来自于最近新的一本佳作《Python网络数据采集》,书中包含与网络爬取相关的所有内容,并提供了大量简例和实例。甚至有一个特别有意思的章节,讲述如何解决验证码检验的问题。
Python的拯救
第一个挑战就是选择爬取信息的平台,本文选择了客涯(Kayak)。我们试过了Momondo, 天巡(Skyscanner), 亿客行(Expedia)和其它一些网站,但是这些网站上的验证码特别变态。
在那些“你是人类吗?”的验证中,尝试了多次选择交通灯、十字路口和自行车后,客涯似乎是最好的选择,尽管短时间内加载太多页面它会跳出安全检查。
我们设法让机器人每4到6个小时查询一次网站,结果一切正常。虽然说不定哪个部分偶尔会出点小问题,但是如果收到验证码,既可以手动解决问题后启动机器人,也可以等待几小时后的自动重启。
如果你是网络爬取新手,或者不知道为何有些网站花费很大力气阻止网络爬取,那么为构建爬虫写下第一行代码前,你一定要多加努力。
谷歌的“网络爬取规范”:
http://lmgtfy.com/?q=web+scraping+etiquette
系紧安全带...
导入并打开Chrome浏览器标签页后,会定义一些循环中会用到的函数。这个架构的构思大概是这样的:
· 一个函数用于启动机器人程序,表明想要搜索的城市和日期。
· 这个函数获得首轮搜索结果,按“最佳”航班排序,然后点击“加载更多结果”。
· 另一个函数会爬取整个页面,并返回一个dataframe数据表。
· 随后重复步骤2和步骤3,得出按“价格”和“航行时间”排序的结果。
· 发送一封简要总结价格(最低价和平均价)的邮件,并将带有这三种排序类型的dataframe数据表保存为一份excel文件。
· 以上所有步骤会在循环中重复,每X小时运行一次。
每个Selenium项目都以一个网页驱动器开始。我们使用Chromedriver驱动器,但还有其它选择。PhantomJS和Firefox也很受欢迎。下载Chromedriver后,将其置于一个文件夹中即可。第一行代码会打开一个空白Chrome标签页。
from time import sleep, strftime
from random import randint
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import smtplib
from email.mime.multipart import MIMEMultipart
# Change this to your own chromedriver path!
chromedriver_path = 'C:/{YOUR PATH HERE}/chromedriver_win32/chromedriver.exe'
driver = webdriver.Chrome(executable_path=chromedriver_path) # This will open the Chrome window
sleep(2)
这些是将用于整个项目的包。使用randint函数令机器人在每次搜索之间随机睡眠几秒钟。这对任何一个机器人来说都是必要属性。如果运行前面的代码,应该打开一个Chrome浏览器窗口,机器人会在其中导航。
一起来做一个快速测试:在另一个窗口上访问客涯网(http://kayak.com),选择往返城市和日期。选择日期时,确保选择的是“+/uploads/title/20231205/656e7e8b7a9e8.jpg3天”。由于在编写代码时考虑到了结果页面,所以如果只想搜索特定日期,很可能需要做一些微小的调整。
点击搜索按钮在地址栏获取链接。它应该类似于下面所使用的链接,将变量kayak定义为url,并从网页驱动器执行get方法,搜索结果就会出现。
无论何时,只要在几分钟内使用get命令超过两到三次,就会出现验证码。实际上可以自己解决验证码,并在下一次验证出现时继续进行想要的测试。从测试来看,第一次搜索似乎一直没有问题,所以如果想运行这份代码,并让它在较长的时间间隔后运行,必须解决这个难题。你并不需要十分钟就更新一次这些价格,对吧?
每个XPath都有陷阱
到目前为止,已经打开了一个窗口,获取了一个网站。为了开始获取价格和其他信息,需要使用XPath或CSS选择器,我们选择了XPath。使用XPath导航网页可能会令人感到困惑,即使使用从inspector视图中直接使用“复制XPath”,但这不是获得所需元素的最佳方法。有时通过“复制XPath”这个方法获得的链接过于针对特定对象,以至于很快就失效了。《Python网络数据采集》一书很好地解释了使用XPath和CSS选择器导航的基础知识。
接下来,用Python选择最便宜的结果。上面代码中的红色文本是XPath选择器,在网页上任意一处右键单击选择“inspect”就可以看到它。在想要查看代码的位置,可以再次右键单击选择“inspect”。
为说明之前所观察到的从“inspector”复制路径的缺陷,请参考以下差异:
1 # This is what the copymethod would return. Right click highlighted rows on the right side and select “copy> Copy XPath”//*[@id=“wtKI/uploads/title/20231205/656e7e8b7a9e8.jpgprice_aTab”]/div[1]/div/div/div[1]/div/span/span
2 # This is what I used todefine the “Cheapest” buttoncheap_results= ‘//a[@data/uploads/title/20231205/656e7e8b7a9e8.jpgcode = “price”]’
第二种方法的简洁性清晰可见。它搜索具有data/uploads/title/20231205/656e7e8b7a9e8.jpgcode等于price属性的元素a。第一种方法查找id等于wtKI/uploads/title/20231205/656e7e8b7a9e8.jpgprice_aTab的元素,并遵循第一个div元素和另外四个div和两个span。这次……会成功的。现在就可以告诉你,id元素会在下次加载页面时更改。每次页面一加载,字母wtKI会动态改变,所以只要页面重新加载,代码就会失效。花些时间阅读XPath,保证你会有收获。
不过,使用复制的方法在不那么“复杂”的网站上工作,也是很好的!
基于以上所展示的内容,如果想在一个列表中以几个字符串的形式获得所有搜索结果该怎么办呢?其实很简单。每个结果都在一个对象中,这个对象的类是“resultWrapper”。获取所有结果可以通过像下面这样的for循环语句来实现。如果你能理解这一部分,应该可以理解接下来的大部分代码。它基本上指向想要的结果(结果包装器),使用某种方式(XPath)获得文本,并将其放置在可读对象中(首先使用flight_containers,然后使用flight_list)。
前三行已展示在图中,并且可以清楚地看到所需的内容,但是有获得信息的更优选择,需要逐一爬取每个元素。
准备起飞吧!
最容易编写的函数就是加载更多结果的函数,所以代码由此开始。为了在不触发安全验证的前提下最大化所获取的航班数量,每次页面显示后,单击“加载更多结果”。唯一的新内容就是所添加的try语句,因为有时按钮加载会出错。如果它对你也有用,只需在前面展示的start_kayak函数中进行简要注释。
# Load more results to maximize the scraping
def load_more():
try:
more_results = '//a[@class = “moreButton”]'
driver.find_element_by_xpath(more_results).click()
# Printing these notes during the program helps me quickly check what it is doing
print('sleeping…..')
sleep(randint(45,60))
except:
pass
现在,经过这么长的介绍,已经准备好定义实际爬取页面的函数。
我们编译了下一个函数page_scrape中的大部分元素。有时这些元素会返回列表插入去程信息和返程信息之间。这里使用了一个简单的办法分开它们,比如在第一个 section_a_list和section_b_list变量中,该函数还返回一个flight_df数据表。所以可以分离在不同分类下得到的结果,之后再把它们合并起来。
def page_scrape():
“““This function takes care of the scraping part”““
xp_sections = '//*[@class=“section duration”]'
sections = driver.find_elements_by_xpath(xp_sections)
sections_list = [value.text for value in sections]
section_a_list = sections_list[::2] # This is to separate the two flights
section_b_list = sections_list[1::2] # This is to separate the two flights
# if you run into a reCaptcha, you might want to do something about it
# you will know there's a problem if the lists above are empty
# this if statement lets you exit the bot or do something else
# you can add a sleep here, to let you solve the captcha and continue scraping
# i'm using a SystemExit because i want to test everything from the start
if section_a_list == []:
raise SystemExit
# I'll use the letter A for the outbound flight and B for the inbound
a_duration = []
a_section_names = []
for n in section_a_list:
# Separate the time from the cities
a_section_names.append(''.join(n.split()[2:5]))
a_duration.append(''.join(n.split()[0:2]))
b_duration = []
b_section_names = []
for n in section_b_list:
# Separate the time from the cities
b_section_names.append(''.join(n.split()[2:5]))
b_duration.append(''.join(n.split()[0:2]))
xp_dates = '//div[@class=“section date”]'
dates = driver.find_elements_by_xpath(xp_dates)
dates_list = [value.text for value in dates]
a_date_list = dates_list[::2]
b_date_list = dates_list[1::2]
# Separating the weekday from the day
a_day = [value.split()[0] for value in a_date_list]
a_weekday = [value.split()[1] for value in a_date_list]
b_day = [value.split()[0] for value in b_date_list]
b_weekday = [value.split()[1] for value in b_date_list]
# getting the prices
xp_prices = '//a[@class=“booking/uploads/title/20231205/656e7e8b7a9e8.jpglink”]/span[@class=“price option/uploads/title/20231205/656e7e8b7a9e8.jpgtext”]'
prices = driver.find_elements_by_xpath(xp_prices)
prices_list = [price.text.replace('$','') for price in prices if price.text != '']
prices_list = list(map(int, prices_list))
# the stops are a big list with one leg on the even index and second leg on odd index
xp_stops = '//div[@class=“section stops”]/div[1]'
stops = driver.find_elements_by_xpath(xp_stops)
stops_list = [stop.text[0].replace('n','0') for stop in stops]
a_stop_list = stops_list[::2]
b_stop_list = stops_list[1::2]
xp_stops_cities = '//div[@class=“section stops”]/div[2]'
stops_cities = driver.find_elements_by_xpath(xp_stops_cities)
stops_cities_list = [stop.text for stop in stops_cities]
a_stop_name_list = stops_cities_list[::2]
b_stop_name_list = stops_cities_list[1::2]
# this part gets me the airline company and the departure and arrival times, for both legs
xp_schedule = '//div[@class=“section times”]'
schedules = driver.find_elements_by_xpath(xp_schedule)
hours_list = []
carrier_list = []
for schedule in schedules:
hours_list.append(schedule.text.split('\n')[0])
carrier_list.append(schedule.text.split('\n')[1])
# split the hours and carriers, between a and b legs
a_hours = hours_list[::2]
a_carrier = carrier_list[1::2]
b_hours = hours_list[::2]
b_carrier = carrier_list[1::2]
cols = (['Out Day', 'Out Time', 'Out Weekday', 'Out Airline', 'Out Cities', 'Out Duration', 'Out Stops', 'Out Stop Cities',
'Return Day', 'Return Time', 'Return Weekday', 'Return Airline', 'Return Cities', 'Return Duration', 'Return Stops', 'Return Stop Cities',
'Price'])
flights_df = pd.DataFrame({'Out Day': a_day,
'Out Weekday': a_weekday,
'Out Duration': a_duration,
'Out Cities': a_section_names,
'Return Day': b_day,
'Return Weekday': b_weekday,
'Return Duration': b_duration,
'Return Cities': b_section_names,
'Out Stops': a_stop_list,
'Out Stop Cities': a_stop_name_list,
'Return Stops': b_stop_list,
'Return Stop Cities': b_stop_name_list,
'Out Time': a_hours,
'Out Airline': a_carrier,
'Return Time': b_hours,
'Return Airline': b_carrier,
'Price': prices_list})[cols]
flights_df['timestamp'] = strftime(“%Y%m%d/uploads/title/20231205/656e7e8b7a9e8.jpg%H%M”) # so we can know when it was scraped
return flights_df
尽量让这些名字容易理解。记住变量a表示旅行的去程信息,变量b表示旅行的返程信息。接下来说说下一个函数。
等等,还有什么吗?
截至目前,已经有了一个能加载更多结果的函数和一个能爬取其他结果的函数。本可以在此结束这篇文章,而你可以自行手动使用这些函数,并在浏览的页面上使用爬取功能。但是前文提到给自己发送邮件和一些其他信息的内容,这都包含在接下来的函数start_kayak中。
它要求填入城市名和日期,并由此打开一个kayak字符串中的地址,该字符串直接跳转到“最佳”航班结果排序页面。第一次爬取后,可以获取价格的顶部矩阵,这个矩阵将用于计算平均值和最小值,之后和客涯(Kayak)的预测结果(页面左上角)一同发送到邮件中。这是单一日期搜索时可能导致错误的原因之一,因其不包含矩阵元素。
def start_kayak(city_from, city_to, date_start, date_end):
“““City codes /uploads/title/20231205/656e7e8b7a9e8.jpg it's the IATA codes!
Date format /uploads/title/20231205/656e7e8b7a9e8.jpg YYYY/uploads/title/20231205/656e7e8b7a9e8.jpgMM/uploads/title/20231205/656e7e8b7a9e8.jpgDD”““
kayak = ('https://www.kayak.com/flights/' + city_from + '/uploads/title/20231205/656e7e8b7a9e8.jpg' + city_to +
'/' + date_start + '/uploads/title/20231205/656e7e8b7a9e8.jpgflexible/' + date_end + '/uploads/title/20231205/656e7e8b7a9e8.jpgflexible?sort=bestflight_a')
driver.get(kayak)
sleep(randint(8,10))
# sometimes a popup shows up, so we can use a try statement to check it and close
try:
xp_popup_close = '//button[contains(@id,”dialog/uploads/title/20231205/656e7e8b7a9e8.jpgclose”) and contains(@class,”Button/uploads/title/20231205/656e7e8b7a9e8.jpgNo/uploads/title/20231205/656e7e8b7a9e8.jpgStandard/uploads/title/20231205/656e7e8b7a9e8.jpgStyle close “)]'
driver.find_elements_by_xpath(xp_popup_close)[5].click()
except Exception as e:
pass
sleep(randint(60,95))
print('loading more.....')
# load_more()
print('starting first scrape.....')
df_flights_best = page_scrape()
df_flights_best['sort'] = 'best'
sleep(randint(60,80))
# Let's also get the lowest prices from the matrix on top
matrix = driver.find_elements_by_xpath('//*[contains(@id,”FlexMatrixCell”)]')
matrix_prices = [price.text.replace('$','') for price in matrix]
matrix_prices = list(map(int, matrix_prices))
matrix_min = min(matrix_prices)
matrix_avg = sum(matrix_prices)/len(matrix_prices)
print('switching to cheapest results…..')
cheap_results = '//a[@data/uploads/title/20231205/656e7e8b7a9e8.jpgcode = “price”]'
driver.find_element_by_xpath(cheap_results).click()
sleep(randint(60,90))
print('loading more…..')
# load_more()
print('starting second scrape…..')
df_flights_cheap = page_scrape()
df_flights_cheap['sort'] = 'cheap'
sleep(randint(60,80))
print('switching to quickest results…..')
quick_results = '//a[@data/uploads/title/20231205/656e7e8b7a9e8.jpgcode = “duration”]'
driver.find_element_by_xpath(quick_results).click()
sleep(randint(60,90))
print('loading more…..')
# load_more()
print('starting third scrape…..')
df_flights_fast = page_scrape()
df_flights_fast['sort'] = 'fast'
sleep(randint(60,80))
# saving a new dataframe as an excel file. the name is custom made to your cities and dates
final_df = df_flights_cheap.append(df_flights_best).append(df_flights_fast)
final_df.to_excel('search_backups//{}_flights_{}/uploads/title/20231205/656e7e8b7a9e8.jpg{}_from_{}_to_{}.xlsx'.format(strftime(“%Y%m%d/uploads/title/20231205/656e7e8b7a9e8.jpg%H%M”),
city_from, city_to,
date_start, date_end), index=False)
print('saved df…..')
# We can keep track of what they predict and how it actually turns out!
xp_loading = '//div[contains(@id,”advice”)]'
loading = driver.find_element_by_xpath(xp_loading).text
xp_prediction = '//span[@class=“info/uploads/title/20231205/656e7e8b7a9e8.jpgtext”]'
prediction = driver.find_element_by_xpath(xp_prediction).text
print(loading+'\n'+prediction)
# sometimes we get this string in the loading variable, which will conflict with the email we send later
# just change it to “Not Sure” if it happens
weird = '¯\\_(ツ)_/¯'
if loading == weird:
loading = 'Not sure'
username = 'YOUREMAIL@hotmail.com'
password = 'YOUR PASSWORD'
server = smtplib.SMTP('smtp.outlook.com', 587)
server.ehlo()
server.starttls()
server.login(username, password)
msg = ('Subject: Flight Scraper\n\n\
Cheapest Flight: {}\nAverage Price: {}\n\nRecommendation: {}\n\nEnd of message'.format(matrix_min, matrix_avg, (loading+'\n'+prediction)))
message = MIMEMultipart()
message['From'] = 'YOUREMAIL@hotmail.com'
message['to'] = 'YOUROTHEREMAIL@domain.com'
server.sendmail('YOUREMAIL@hotmail.com', 'YOUROTHEREMAIL@domain.com', msg)
print('sent email…..')
虽然没有使用Gmail账户测试发送邮件,但是可以搜索到很多的替代方法,前文提到的那本书中也有其他方法来实现这一点。如果已有一个Hotmail账户,只要替换掉个人的详细信息,它就会开始工作了。
如果想探索脚本的某一部分正在做什么,可以将脚本复制下来并在函数外使用它。这是彻底理解它的唯一方法。
利用刚才创造的一切
在这些步骤之后,还可以想出一个简单的循环来使用刚创造的函数,同时使其持续运行。完成四个“花式”提示,写下城市和日期(输入)。因为测试时不想每次都输入这些变量,需要的时候可以使用以下这个清楚的方式进行替换。
如果已经做到了这一步,恭喜你!改进还有很多,比如与Twilio集成,发送文本消息而不是邮件。也可以使用VPN或更加难懂的方式同时从多个服务器上研究搜索结果。还有就是验证码的问题,验证码会时不时地跳出来,但对此类问题还是有解决办法的。不过,能走到这里已经是有很牢固的基础了,你可以尝试添加一些额外的要素。
使用脚本运行测试的示例
留言 点赞 关注
我们一起分享AI学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”
俄罗斯的互联网行业发展如何?
谢谢邀请。战斗民族俄罗斯充满着广袤、神秘、伟大、瑰丽的色彩;俄式建筑浑圆饱满、富丽堂皇;俄罗斯的美女金发碧眼、丰乳肥臀、挺拔俊美,在全世界有很高的声誉,美国人形容的天堂生活是:领美国工资,娶俄罗斯美女,住英国房子,吃中国饮食。连俄罗斯人自己也笑言美女是本国特产。
那么俄罗斯的互联网行业发展怎样呢?
我们来看一下:
1、起步跟中国差不多时间,目前发展跟同样迅速。
① 俄罗斯互联网始于1994年4月7日,比中国正式接入互联网还要早两个星期。中国是1994年4月20日,通过一条64K的国际专线接入国际互联网,从而开启了中国的互联网时代;而同年的4月7日,以俄罗斯国家域名.RU的正式注册,标志着战斗民族的互联网正式诞生。
② 俄罗斯目前互联网、电子商务高速发展
目前俄罗斯互联网普及率达66%,近年智能手机和移动互联网的普及,带来大量的手机上网用户,俄罗斯现有互联网用户总数近1亿,且每年以7.4%的速度增长。半数以上用户年龄在35岁以下。
俄电子通讯技术协会与俄高等经济学院联合发布的《俄罗斯互联网/uploads/title/20231205/656e7e8b7a9e8.jpg2013》报告指出,该年俄互联网产业规模已达上万亿卢布,加上互联网周边产业,其总规模可达5.2万亿卢布,占俄罗斯GDP8.5%左右。报告预测,至2018年俄罗斯互联网产业年均增长幅度可达15/uploads/title/20231205/656e7e8b7a9e8.jpg20%。
根据俄罗斯电子通讯联合会和国家科研机构"高等经济学院"共同推出的《2014/uploads/title/20231205/656e7e8b7a9e8.jpg2015年俄罗斯互联网经济调查研究报告》,2014年俄罗斯传统互联网市场规模为10940亿卢布,电子支付市场规模为4760亿卢布,互联网经济已达年GDP总量的2.2%。互联网相关市场的容量为118000亿卢布,约等于GDP总量的16%。
根据2015年俄罗斯InSales.ru公司提交的分析数据显示,2014年俄罗斯电子商务规模达6120亿卢布,比上年增长了31%。虽然2015/uploads/title/20231205/656e7e8b7a9e8.jpg2016年俄罗斯经济面临严重下滑,但专家预测,至2018年俄罗斯电子商务依然能够保持年均8/uploads/title/20231205/656e7e8b7a9e8.jpg10%的增幅。
2、俄罗斯有哪些大型互联网公司?
在包括社交、搜索、电商等领域,俄罗斯都有着不错的互联网公司。
① Yandex
Yandex公司,成立于1993年,比中国的BAT三巨头都要早,比国内新浪、网易、搜狐三大门户网站也早5年时间。
Yandex目前所提供的服务包括搜索、最新新闻、地图和百科、电子信箱、电子商务、互联网广告及其他服务。Yandex在俄罗斯本地搜索引擎的市场份额占到65%,远超俄罗斯Google22%。2011年5月在美国纳斯达克上市,员工规模达到5600多人,市值80亿美元。
②VK(原VKontakte)
VK.com 创立于2006年,是俄罗斯最大的社交网站,发展至今已经不仅仅是大学生和高校毕业生的联系网,它已经成长为仅次于搜索引擎Yandex的俄罗斯第二大网站。根据eBizMBA排名,为世界第八大受欢迎的社交网站,2014年被http://Mail.ru收购。
③ Mail.ru
Mail.ru是俄罗斯本土最大的门户网站,在《福布斯》2月发布的俄罗斯互联网公司价值排名中,Mail.ru以47亿美元首次超过Yandex成为俄罗斯市值最高的互联网公司。腾讯是其的股东之一,拥有7.8%的集团股份。
④ Ozon.ru
Ozon.ru是俄罗斯最大的在线零售商。于1998年由俄公司Reksoft成立。它主要商品是书籍,电子产品,音乐和电影。公司主要股东包括:Baring Vostock、Index Ventures、ru/uploads/title/20231205/656e7e8b7a9e8.jpgNet、乐天、英特尔投资、Holzbrink和思科。2005年,Ozon销售额大约为1930万美元。2007年4月,Ozon.ru从霍尔茨布林克风险投资公司,Index Ventures和Baring Vostok募集到1800万美元风险投资资金。2011年9月,Ozon将1亿美元投资到其4个主要业务:在线零售商Ozon.ru;船舶公司O/uploads/title/20231205/656e7e8b7a9e8.jpgcourier;Ozon;旅游、鞋和配件零售商 Sapato.ru。2012年,公司拥有1450万注册用户和2.5亿美元收入,并聘请1700名员工。
⑤ Яндекс
Яндекс创立于2000年,Yandex公司的子公司,公司市值43亿美金,该平台共有18000家商家入驻。Yandex积极在其它业务领域布局来扩大其非广告领域营收占比。公司收购俄罗斯最大的汽车门户网站Auto.ru后,涉足出租车服务业务,将"Яндекс.Такси"单拆出来成立为子公司,砍掉了"Яндекс.Мастер"项目,从物流服务商Multiship手中购买并成立自己的电商配送平台"Яндекс.Доставку",该平台为电商商家提供发货服务。
⑥ Avito
Avito是一家创立于2007年的全球第三大分类信息发布平台。这家公司目前占有俄罗斯分类信息市场25%的流量与15%的产值。2012年,Avito产生了约3000万美元的销售收入。随着Slando与OLX的并入,Avito有望依靠更加庞大与稳定的用户群获得营收,因此广告将成为未来收入的重要组成部分。目前,Avito拥有14万日活跃用户,但其中有4万用户以前从不使用这项服务。
以上是俄罗斯本土比较著名的互联网公司。而中美的互联网公司在俄罗斯也有着不错的市场,比如google、Facebook、eBay、Aliexpress(阿里速卖通)等等。
如何实现一键调用以太坊智能合约?
有人问如何用按钮调用智能合约,在这篇文章中,我将给出一个简单但有希望有效的演示,说明JavaScript开发人员如何创建一个能够调用智能合约的网页,并通过单击即可向其汇款(以太币)。
要做到这一点,用户将需要使用支持Web3的浏览器,因此可以使用像Parity或Mist这样的可安装浏览器,或者他们可以使用像MetaMask这样的浏览器扩展。
此外,虽然我可以教你如何直接使用web3API,但我将教你如何使用一个新的很方便的库与以太网智能合约EthJS交互。
你的网站需要等待ready事件,然后检查全局web3对象。看起来像这样:
window.addEventListener('load', function() {
// Check if Web3 has been injected by the browser:
if (typeof web3 !== 'undefined') {
// You have a web3 browser! Continue below!
startApp(web3);
} else {
// Warn the user that they need to get a web3 browser
// Or install MetaMask, maybe with a nice graphic.
})
从MetaMask开发人员指南中复制。
在这个例子中,我假设你正在使用像Browserify或Webpack这样的JavaScript捆绑器,并且知道如何从NPM安装模块。
在你的应用程序设置中,你将使用一些不同的ethjs模块,你将使用全局web3对象的currentProvider属性初始化它们,这就是它与区块链的对话方式。
const Eth = require('ethjs/uploads/title/20231205/656e7e8b7a9e8.jpgquery')
const EthContract = require('ethjs/uploads/title/20231205/656e7e8b7a9e8.jpgcontract')
function startApp(web3) {
const eth = new Eth(web3.currentProvider)
const contract = new EthContract(eth)
initCon
一旦实例化了合约,就可以使用它来创建对网络上实时合约的引用。要做到这一点,你需要两件事:
合约地址。
合约ABI。
ABI是应用程序二进制接口,它告诉你的JavaScript如何与智能合约通信。它只是描述合约方法的JSON数据。
通常,如果你发布合约,你知道如何获得ABI,如果你要与其他人签订合约,他们应该提供ABI,尽管有时候你可以找到与Etherscan等区块浏览器的合约相匹配的ABI。
假设你有可用的ABI和地址,并了解我们现在如何创建合约对象。在这个例子中,我将使用仅包含Token标准中的transfer(to,value)方法的ABI:
const abi = [{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
}]
const address = '0xdeadbeef123456789000000000000'
function initContract (contract) {
const MiniToken = contract(abi)
const miniToken = MiniToken.at(address)
listenForClicks(miniToken)
}
现在我们已经为智能合约初始化了一个JavaScript接口,所以我们只需要创建一个小HTML。
还有一点JavaScript来响应点击,并发送这些资金:
function listenForClicks (miniToken) {
var button = document.querySelector('button.transferFunds')
button.addEventListener('click', function() {
miniToken.transfer(toAddress, value, { from: addr })
.then(function (txHash) {
console.log('Transaction sent')
console.dir(txHash)
waitForTxToBeMined(txHash)
})
.catch(console.error)
})
}
请注意,如果此交易也发送以太,你将向包含from字段的选项哈希添加value:'10000'。该值以wei为单位,为1x10 ^ /uploads/title/20231205/656e7e8b7a9e8.jpg18以太。转换的简单方法是这样的:
var inWei = web3.toWei('10', 'ether')
对于普通的Web开发人员来说,一个奇怪的部分是交易响应并不意味着交易现在已经完成,它只是意味着它已经被传输到网络。它仍然需要被挖掘,而在以太坊中,平均需要大约14秒(阻断时间,查看EthStats.net上的统计数据)。
现在还没有很好的订阅方法可以等待挖掘交易,所以你需要用收到的txHash进行轮询。是的,这很乏味,所以我会告诉你如何使用新的JavaScript async/await模式来减少痛苦:
async function waitForTxToBeMined (txHash) {
let txReceipt
while (!txReceipt) {
try {
txReceipt = await eth.getTransactionReceipt(txHash)
} catch (err) {
return indicateFailure(err)
}
}
indicateSuccess()
}
如此而已!
哪个电子货币兑换平台比较可靠?
这家平台应该是比较安全的,不过还是慎重点好。黄金电子货币兑换中心是专门从事于在线电子货币兑换,充值和提现等服务的网站,专业为国内外用户提供Perfectmoney(PM),Webmoney(WMZ), Payeer(PR), OKPay, BTC/uploads/title/20231205/656e7e8b7a9e8.jpgE USD, AdvCash等国际主流电子货币双向兑换。
现在国内外的一些平台也已经上线了比特币、瑞泰币、莱特币、活力币等数字加密货币。
webmoney哪里买?
Webmoney是日本最流行的网上货币几乎适用于所有的日本游戏支付及网上购物
支持的日服游戏:
天翼之链 惊天动地(CABAL) 神魔传说(RAPPELZ) MOE 挑战(DEKARON)红石(REDSTONE) 冒险岛 ECO 英雄 M2机甲演义 巨商传 行会战争(GUILDWAR) 希望 石器时代2 宿命传说 热血江湖 破天一剑 地下城与勇者 轩辕传飞天历险 梦幻之星OL/uploads/title/20231205/656e7e8b7a9e8.jpgBB 最终幻想FF11 墨香 洛奇 冒险岛 仙境传说 LAPIS 天堂1 天堂2 LOST ROHAN 卓越之剑 等。
网上购物:
可以和日本最大的商业购物网站乐天市场http://www.rakuten.co.jp/进行自由点数互转,自由享受网上购物的乐趣
乐天市场:日本最大的网上商店街,汇集了10000家以上的网上商店,商品500万种以上。在乐天的共同购物里面可以买到很便宜的日常生活用品。
fit测试主要目的?
在软件开发的生命周期中,每个人都对质量负有责任。理想情况下,开发人员在开发周期中,用像 Junit 和 TestNG 这样的测试工具保证早期质量,而质量保证团队用功能性系统测试在周期末端跟进,使用像 Selenium 这样的工具。但是即使拥有优秀的质量保证,有些应用程序在交付的时候仍然被认为是质量低下的。为什么呢?因为它们并没有做它们应当做的事。
在客户、(编写应用程序需求的)业务部门和(实现需求的)开发团队之间的沟通错误,通常是摩擦的原因,有时还是开发项目彻底失败的常见原因。幸运的是,存在一些方法可以帮助需求作者和实现者之间尽早 沟通。
FIT 化的解决方案
集成测试框架 (FIT)是一个测试平台,可以帮助需求编写人员和把需求变成可执行代码的人员之间的沟通。使用 FIT,需求被做成表格模型,充当开发人员编写的测试的数据模型。表格本身充当输入和测试的预期输出。
下载 FIT
集成测试框架(FIT)最初是由 Ward Cunningham 创建的,他就是 wiki 的发明人。请访问 Cunningham 的 Web 站点了解关于 FIT 的更多知识并 免费下载它。
图 1 显示了用 FIT 创建的结构化模型。第一行是测试名称,下一行的三列是与输入(value1 和 value2)和预期结果(trend())有关的标题。
图 1. 用 FIT 创建的结构化模型
好消息是,对于编程没有经验的人也能编写这个表格。FIT 的设计目的就是让消费者或业务团队在开发周期中,尽早与实现他们想法的开发人员协作。创建应用程序需求的简单表格式模型,可以让每个人清楚地看出代码和需求是否是一致的。
清单 1 是与图 1 的数据模型对应的 FIT 代码。不要太多地担心细节 —— 只要注意代码有多么简单,而且代码中没有包含验证逻辑(例如,断言等)。可能还会注意到一些与表 1 中的内容匹配的变量和方法名称;关于这方面的内容后面介绍。
清单 1. 根据 FIT 模型编写的代码
package test.com.acme.fit.impl;
import com.acme.sedlp.trend.Trender;
import fit.ColumnFixture;
public class TrendIndicatorextends ColumnFixture {
public double value1;
public double value2;
public String trend(){
return Trender.determineTrend(value1, value2).getName();
}
}
清单 1 中的代码由研究上面表格并插入适当代码的开发人员编写。最后,把所有东西合在一起,FIT 框架读取表 1 的数据,调用对应的代码,并确定结果。
FIT 和 JUnit
FIT 的优美之处在于,它让组织的消费者或业务端能够尽早参与测试过程(例如,在开发期间)。JUnit 的力量在于编码过程中的单元测试,而 FIT 是更高层次的测试工具,用来判断规划的需求实现的正确性。
例如,虽然 JUnit 擅长验证两个 Money 对象的合计与它们的两个值的合计相同,但 FIT 可以验证总的订单价格是其中商品的价格减去任何相关折扣之后的合计。区别虽然细微,但的确重大!在 JUnit 示例中,要处理具体的对象(或者需求的实现),但是使用 FIT 时要处理的是高级的业务过程。
这很有意义,因为编写需求的人通常不太考虑 Money 对象 —— 实际上,他们可能根本不知道这类东西的存在!但是,他们确实要考虑,当商品被添加到订单时,总的订单价格应当是商品的价格减去所有折扣。
FIT 和 JUnit 之间绝不是竞争关系,它们是保证代码质量的好搭档,正如在后面的 案例研究中将要看到的。
测试用的 FIT 表格
表格是 FIT 的核心。有几种不同类型的表格(用于不同的业务场景),FIT 用户可以用不同的格式编写表格。用 HTML 编写表格甚至用 Microsoft Excel 编写都是可以的,如图 2 所示:
图 2. 用 Microsoft Excel 编写的表格
也有可能用 Microsoft Word 这样的工具编写表格,然后用 HTML 格式保存,如图 3 所示:
图 3. 用 Microsoft Word 编写的表格
开发人员编写的用来执行表格数据的代码叫作装备(fixture)。要创建一个装备类型,必须扩展对应的 FIT 装备,它映射到对应的表。如前所述,不同类型的表映射到不同的业务场景。
用装备进行装配
最简单的表和装备组合,也是 FIT 中最常用的,是一个简单的列表格,其中的列映射到预期过程的输入和输出。对应的装备类型是 ColumnFixture。
如果再次查看 清单 1,将注意到 TrendIndicator 类扩展了 ColumnFixture,而且也与图 3 对应。请注意在图 3 中,第一行的名称匹配完全限定名称(test.com.acme.fit.impl.TrendIndicator)。下一行有三列。头两个单元格的值匹配 TrendIndicator 类的 public 实例成员(value1 和 value2),最后一个单元格的值只匹配 TrendIndicator 中的方法(trend)。
现在来看清单 1 中的 trend 方法。它返回一个 String 值。可以猜测得到,对于表中每个剩下的行,FIT 都会替换值并比较结果。在这个示例中,有三个 “数据” 行,所以 FIT 运行 TrendIndicator 装备三次。第一次,value1 被设置成 84.0,value2 设置成 71.2。然后 FIT 调用 trend 方法,并把从方法得到的值与表中的值比较,应当是 “decreasing”。
通过这种方式,FIT 用装备代码测试 Trender 类,每次 FIT 执行 trend 方法时,都执行类的 determineTrend 方法。当代码测试完成时,FIT 生成如图 4 所示的报告:
图 4. FIT 报告 trend 测试的结果
trend 列单元格的绿色表明测试通过(例如,FIT 设置 value1为 84.0,value2 为 71.2,调用 trend 得到返回值 “decreasing”)。
查看 FIT 运行
可以通过命令行,用 Ant 任务并通过 Maven 调用 FIT,从而简单地把 FIT 测试插入构建过程。因为自动进行 FIT 测试,就像 JUnit 测试一样,所以也可以定期运行它们,例如在持续集成系统中。
最简单的命令行运行器,如清单 2 所示,是 FIT 的 FolderRunner,它接受两个参数 —— 一个是 FIT 表格的位置,一个是结果写入的位置。不要忘记配置类路径!
清单 2. FIT 的命令行
%>java fit.runner.FolderRunner ./test/fit ./target/
FIT 通过插件,还可以很好地与 Maven 一起工作,如清单 3 所示。只要下载插件,运行 fit:fit命令,就 OK 了!(请参阅 参考资料 获得 Maven 插件。)
清单 3. Maven 得到 FIT
C:/dev/proj/edoa>maven fit:fit
__ __
| // |__ _Apache__ ___
| |//| / _` / V / /uploads/title/20231205/656e7e8b7a9e8.jpg_) ' / ~ intelligent projects ~
|_| |_/__,_|/_//___|_||_| v. 1.0.2
build:start:
java:prepare/uploads/title/20231205/656e7e8b7a9e8.jpgfilesystem:
java:compile:
[echo] Compiling to C:/dev/proj/edoa/target/classes
java:jar/uploads/title/20231205/656e7e8b7a9e8.jpgresources:
test:prepare/uploads/title/20231205/656e7e8b7a9e8.jpgfilesystem:
test:test/uploads/title/20231205/656e7e8b7a9e8.jpgresources:
test:compile:
fit:fit:
[java] 2 right, 0 wrong, 0 ignored, 0exceptions
BUILD SUCCESSFUL
Total time: 4 seconds
Finished at: Thu Feb 02 17:19:30 EST 2006
试用 FIT:案例研究
现在已经了解了 FIT 的基础知识,我们来做一个练习。如果还没有 下载 FIT,现在是下载它的时候了!如前所述,这个案例研究显示出可以容易地把 FIT 和 JUnit 测试组合在一起,形成多层质量保证。
假设现在要为一个酿酒厂构建一个订单处理系统。酿酒厂销售各种类型的酒类,但是它们可以组织成两大类:季节性的和全年性的。因为酿酒厂以批发方式运作,所以酒类销售都是按桶销售的。对于零售商来说,购买多桶酒的好处就是折扣,而具体的折扣根据购买的桶数和酒是季节性还是全年性的而不同。
麻烦的地方在于管理这些需求。例如,如果零售店购买了 50 桶季节性酒,就没有折扣;但是如果这 50 桶不是 季节性的,那么就有 12% 的折扣。如果零售店购买100 桶季节性酒,那就有折扣,但是只有 5%。100 桶更陈的非季节性酒的折扣达到 17%。购买量达到 200 时,也有类似的规矩。
对于开发人员,像这样的需求集可能让人摸不着头脑。但是请看,我们的啤酒/uploads/title/20231205/656e7e8b7a9e8.jpg酿造行业分析师用 FIT 表可以很容易地描述出这个需求,如图 5 所示:
图 5. 我的业务需求非常清晰:
表格语义
这个表格从业务的角度来说很有意义,它确实很好地规划出需求。但是作为开发人员,还需要对表格的语言了解更多一些,以便从表格得到值。首先,也是最重要的,表格中的初始行说明表格的名称,它恰好与一个匹配的类对应(org.acme.store.discount.DiscountStructureFIT)。命名要求表格作者和开发人员之间的一些协调。至少,需要指定完全限定的表格名称(也就是说,必须包含包名,因为 FIT 要动态地装入对应的类)。
请注意表格的名称以 FIT 结束。第一个倾向可能是用 Test结束它,但要是这么做,那么在自动环境中运行 FIT 测试和 JUnit 测试时,会与 JUnit 产生些冲突,JUnit 的类通常通过命名模式查找,所以最好避免用 Test 开始或结束 FIT 表格名称。
下一行包含五列。每个单元格中的字符串都特意用斜体格式,这是 FIT 的要求。前面学过,单元格名称与装备的实例成员和方法匹配。为了更简洁,FIT 假设任何值以括号结束的单元格是方法,任何值不以括号结束的单元格是实例成员。
特殊智能
FIT 在处理单元格的值,进行与对应装备类的匹配时,采用智能解析。如 图 5 所示,第二行单元格中的值是用普通的英文编写的,例如 “number of cases”。FIT 试图把这样的字符串按照首字母大写方式连接起来;例如,“number of cases” 变成 “numberOfCases”,然后 FIT 试图找到对应的装备类。这个原则也适用于方法 —— 如图 5 所示,“discount price()” 变成了 “discountPrice()”。
FIT 还会智能地猜测单元格中值的具体类型。例如,在 图 5 余下的八行中,每一列都有对应的类型,或者可以由 FIT 准确地猜出,或者要求一些定制编程。在这个示例中,图 5 有三种不同类型。与 “number of cases” 关联的列匹配到 int,而与 “is seasonal” 列关联的值则匹配成 boolean。
剩下的三列,“list price per case”、“discount price()” 和 “discount amount()” 显然代表当前值。这几列要求定制类型,我将把它叫作 Money。有了它之后,应用程序就要求一个代表钱的对象,所以在我的 FIT 装备中遵守少量语义就可以利用上这个对象!
FIT 语义总结
表 1 总结了命名单元格和对应的装备实例变量之间的关系:
表 1. 单元格到装备的关系:实例变量
单元格值对应的装备实例变量类型list price per caselistPricePerCaseMoneynumber of casesnumberOfCasesintis seasonalisSeasonalboolean
表 2 总结了 FIT 命名单元格和对应的装备方法之间的关系:
表 2. 单元格到装备的关系:方法
表格单元格的值对应的装备方法返回类型discount price()discountPriceMoneydiscount amount()discountAmountMoney
该构建了!
要为酿酒厂构建的订单处理系统有三个主要对象:一个 PricingEngine 处理包含折扣的业务规则,一个 WholeSaleOrder 代表订单,一个 Money 类型代表钱。
Money 类
第一个要编写的类是 Money类,它有进行加、乘和减的方法。可以用 JUnit 测试新创建的类,如清单 14 所示:
清单 4. JUnit 的 MoneyTest 类
package org.acme.store;
import junit.framework.TestCase;
public class MoneyTest extendsTestCase {
public void testToString()throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
assertEquals("$100.00", total.toString());
}
public void testEquals() throwsException{
Money money = Money.parse("$10.00");
Money control = new Money(10.00);
assertEquals(control, money);
}
public void testMultiply()throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
Money discountAmount = total.mpy(0.05);
assertEquals("$5.00", discountAmount.toString());
}
public void testSubtract()throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
Money discountAmount = total.mpy(0.05);
Money discountedPrice = total.sub(discountAmount);
assertEquals("$95.00", discountedPrice.toString());
}
}
WholeSaleOrder 类
然后,定义 WholeSaleOrder 类型。这个新对象是应用程序的核心:如果 WholeSaleOrder 类型配置了桶数、每桶价格和产品类型(季节性或全年性),就可以把它交给 PricingEngine,由后者确定对应的折扣并相应地在 WholeSaleOrder 实例中配置它。
WholesaleOrder 类的定义如清单 5 所示:
清单 5. WholesaleOrder 类
package org.acme.store.discount.engine;
import org.acme.store.Money;
public class WholesaleOrder {
private int numberOfCases;
private ProductType productType;
private Money pricePerCase;
private double discount;
public double getDiscount() {
return discount;
}
public void setDiscount(doublediscount) {
this.discount = discount;
}
public Money getCalculatedPrice() {
Money totalPrice = this.pricePerCase.mpy(this.numberOfCases);
Money tmpPrice = totalPrice.mpy(this.discount);
return totalPrice.sub(tmpPrice);
}
public Money getDiscountedDifference() {
Money totalPrice = this.pricePerCase.mpy(this.numberOfCases);
returntotalPrice.sub(this.getCalculatedPrice());
}
public int getNumberOfCases() {
return numberOfCases;
}
public void setNumberOfCases(intnumberOfCases) {
this.numberOfCases = numberOfCases;
}
public voidsetProductType(ProductType productType) {
this.productType = productType;
}
public String getProductType() {
return productType.getName();
}
public voidsetPricePerCase(Money pricePerCase) {
this.pricePerCase = pricePerCase;
}
public Money getPricePerCase() {
return pricePerCase;
}
}
从清单 5 中可以看到,一旦在 WholeSaleOrder 实例中设置了折扣,就可以通过分别调用 getCalculatedPrice 和 getDiscountedDifference 方法得到折扣价格和节省的钱。
更好地测试这些方法(用 JUnit)!
定义了 Money 和 WholesaleOrder 类之后,还要编写 JUnit 测试来验证 getCalculatedPrice 和 getDiscountedDifference 方法的功能。测试如清单 6 所示:
清单 6. JUnit 的 WholesaleOrderTest 类
packageorg.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
importorg.acme.store.discount.engine.WholesaleOrder;
public class WholesaleOrderTestextends TestCase {
/*
* Test method for 'WholesaleOrder.getCalculatedPrice()'
*/
public void testGetCalculatedPrice() {
WholesaleOrder order = newWholesaleOrder();
order.setDiscount(0.05);
order.setNumberOfCases(10);
order.setPricePerCase(new Money(10.00));
assertEquals("$95.00", order.getCalculatedPrice().toString());
}
/*
* Test method for 'WholesaleOrder.getDiscountedDifference()'
*/
public void testGetDiscountedDifference() {
WholesaleOrder order = newWholesaleOrder();
order.setDiscount(0.05);
order.setNumberOfCases(10);
order.setPricePerCase(new Money(10.00));
assertEquals("$5.00", order.getDiscountedDifference().toString());
}
}
PricingEngine 类
PricingEngine 类利用业务规则引擎,在这个示例中,是 Drools(请参阅 “关于 Drools”)。PricingEngine 极为简单,只有一个 public 方法:applyDiscount。只要传递进一个 WholeSaleOrder 实例,引擎就会要求 Drools 应用折扣,如清单 7 所示:
清单 7. PricingEngine 类
package org.acme.store.discount.engine;
import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.drools.io.RuleBaseLoader;
public class PricingEngine {
private static final String RULES="BusinessRules.drl";
private static RuleBase businessRules;
private static void loadRules()throws Exception{
if (businessRules==null){
businessRules = RuleBaseLoader.
loadFromUrl(PricingEngine.class.getResource(RULES));
}
}
public static voidapplyDiscount(WholesaleOrder order) throws Exception{
loadRules();
WorkingMemory workingMemory = businessRules.newWorkingMemory( );
workingMemory.assertObject(order);
workingMemory.fireAllRules();
}
}
关于 Drools
Drools 是一个为 Java™ 语言度身定制的规则引擎实现。它提供可插入的语言实现,目前规则可以用 Java、Python 和 Groovy 编写。要获得更多信息,或者下载 Drools,请参阅 Drools 主页。
Drools 的规则
必须在特定于 Drools 的 XML 文件中定义计算折扣的业务规则。例如,清单 8 中的代码段就是一个规则:如果桶数大于 9,小于 50,不是季节性产品,则订单有 5% 的折扣。
清单 8. BusinessRules.drl 文件的示例规则
xmlns="http://drools.org/rules" xmlns:java="http://drools.org/semantics/java" xmlns:xs="http://www.w3.org/2001/XMLSchema/uploads/title/20231205/656e7e8b7a9e8.jpginstance" xs:schemaLocation="http://drools.org/rules rules.xsd http://drools.org/semantics/java java.xsd"> order.setDiscount(0.05);
标记团队测试
有了 PricingEngine 并定义了应用程序规则之后,可能渴望验证所有东西都工作正确。现在问题就变成,用 JUnit 还是 FIT?为什么不两者都用呢?通过 JUnit 测试所有组合是可能的,但是要进行许多编码。最好是用 JUnit 测试少数几个值,迅速地验证代码在工作,然后依靠 FIT 的力量运行想要的组合。请看看当我这么尝试时发生了什么,从清单 9 开始:
清单 9. JUnit 迅速地验证了代码在工作
packageorg.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
importorg.acme.store.discount.engine.PricingEngine;
importorg.acme.store.discount.engine.ProductType;
importorg.acme.store.discount.engine.WholesaleOrder;
public class DiscountEngineTestextends TestCase {
public void testCalculateDiscount() throwsException{
WholesaleOrder order = newWholesaleOrder();
order.setNumberOfCases(20);
order.setPricePerCase(new Money(10.00));
order.setProductType(ProductType.YEAR_ROUND);
PricingEngine.applyDiscount(order);
assertEquals(0.05, order.getDiscount(), 0.0);
}
public void testCalculateDiscountNone() throws Exception{
WholesaleOrder order = newWholesaleOrder();
order.setNumberOfCases(20);
order.setPricePerCase(new Money(10.00));
order.setProductType(ProductType.SEASONAL);
PricingEngine.applyDiscount(order);
assertEquals(0.0, order.getDiscount(), 0.0);
}
}
还没用 FIT?那就用 FIT!
在 图 5 的 FIT 表格中有八行数据值。可能已经在 清单 7 中编写了前两行的 JUnit 代码,但是真的想编写整个测试吗?编写全部八行的测试或者在客户添加新规则时再添加新的测试,需要巨大的耐心。好消息就是,现在有了更容易的方法。不过,不是忽略测试 —— 而是用 FIT!
FIT 对于测试业务规则或涉及组合值的内容来说非常漂亮。更好的是,其他人可以完成在表格中定义这些组合的工作。但是,在为表格创建 FIT 装备之前,需要给 Money 类添加一个特殊方法。因为需要在 FIT 表格中代表当前货币值(例如,像 $100.00 这样的值),需要一种方法让 FIT 能够认识 Money 的实例。做这件事需要两步:首先,必须把 static parse 方法添加到定制数据类型,如清单 10 所示:
清单 10. 添加 parse 方法到 Money 类
public static Money parse(String value){
return newMoney(Double.parseDouble(StringUtils.remove(value, '
Money 类的 parse 方法接受一个 String 值(例如,FIT 从表格中取出的值)并返回配置正确的 Money 实例。在这个示例中,$ 字符被删除,剩下的 String 被转变成 double,这与 Money 中现有的构造函数匹配。
不要忘记向 MoneyTest 类添加一些测试来来验证新添加的 parse 方法按预期要求工作。两个新测试如清单 11 所示:
清单 11. 测试 Money 类的 parse 方法
public void testParse() throwsException{
Money money = Money.parse("$10.00");
assertEquals("$10.00", money.toString());
}
public void testEquals() throwsException{
Money money = Money.parse("$10.00");
Money control = new Money(10.00);
assertEquals(control, money);
}
编写 FIT 装备
现在可以编写第一个 FIT 装备了。实例成员和方法已经在表 1 和表 2 中列出,所以只需要把事情串在一起,添加一两个方法来处理定制类型:Money。为了在装备中处理特定类型,还需要添加另一个 parse 方法。这个方法的签名与前一个略有不同:这个方法是个对 Fixture 类进行覆盖的实例方法,这个类是 ColumnFixture 的双亲。
请注意在清单 12 中,DiscountStructureFIT 的 parse方法如何比较 class 类型。如果存在匹配,就调用 Money 的定制 parse 方法;否则,就调用父类(Fixture)的 parse 版本。
清单 12 中剩下的代码是很简单的。对于图 5 所示的 FIT 表格中的每个数据行,都设置值并调用方法,然后 FIT 验证结果!例如,在 FIT 测试的第一次运行中,DiscountStructureFIT 的 listPricePerCase 被设为 $10.00,numberOfCases 设为 10,isSeasonal 为 true。然后执行 DiscountStructureFIT 的 discountPrice,返回的值与 $100.00 比较,然后执行 discountAmount,返回的值与 $0.00 比较。
清单 12. 用 FIT 进行的折扣测试
package org.acme.store.discount;
import org.acme.store.Money;
importorg.acme.store.discount.engine.PricingEngine;
importorg.acme.store.discount.engine.ProductType;
importorg.acme.store.discount.engine.WholesaleOrder;
import fit.ColumnFixture;
public class DiscountStructureFITextends ColumnFixture {
public Money listPricePerCase;
public int numberOfCases;
public boolean isSeasonal;
public Money discountPrice() throwsException {
WholesaleOrder order = this.doOrderCalculation();
return order.getCalculatedPrice();
}
public Money discountAmount() throwsException {
WholesaleOrder order = this.doOrderCalculation();
return order.getDiscountedDifference();
}
/**
* required by FIT for specific types
*/
public Object parse(String value, Classtype) throws Exception {
if (type == Money.class) {
return Money.parse(value);
} else {
return super.parse(value, type);
}
}
private WholesaleOrderdoOrderCalculation() throws Exception {
WholesaleOrder order = newWholesaleOrder();
order.setNumberOfCases(numberOfCases);
order.setPricePerCase(listPricePerCase);
if (isSeasonal) {
order.setProductType(ProductType.SEASONAL);
} else {
order.setProductType(ProductType.YEAR_ROUND);
}
PricingEngine.applyDiscount(order);
return order;
}
}
现在,比较 清单 9 的 JUnit 测试用例和清单 12。是不是清单 12 更有效率?当然可以 用 JUnit 编写所有必需的测试,但是 FIT 可以让工作容易得多!如果感觉到满意(应当是满意的!),可以运行构建,调用 FIT 运行器生成如图 6 所示的结果:
ozon支付方式怎么设置?
1. ozon支付方式可以通过进入ozon平台的设置页面,选择“支付设置”,进入支付方式的设置页面。
2. 在支付方式的设置页面中,可以设置自己的银行卡、信用卡等支付方式,同时还可以选择使用第三方支付机构,如支付宝、微信支付等。
3. 延伸内容:在进行支付方式的设置时,需要注意保护自己的账户安全,确认自己的支付方式不会被他人盗用。
同时,对于某些特殊购物的场景,例如大额支付,可以选择更加安全的支付方式,例如银行转账等。
本站涵盖的内容、图片等数据系网络收集,部分未能与原作者取得联系。若涉及版权问题,请联系ynstorm@foxmail.com进行删除!