整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

写个网页更简单了!让AI根据手绘原型生成HTML - 教程+代码

小新 编译自 Insight Data Blog

量子位 出品 | 公众号 QbitAI

写个网页能有多麻烦?在大多数公司里,这项工作分为三步:

1. 产品经理完成用户调研任务后,列出一系列技术要求;

2. 设计师根据这些要求来设计低保真原型,逐渐修改得到高保真原型和UI设计图;

3. 工程师将这些设计图实现为代码,最终变成用户使用的产品。

这么多环节,任何地方出一点问题,都会拉长开发周期。因此,不少公司,比如Airbnb已经开始用机器学习来提高这个过程的效率。

Airbnb内部的AI工具,从图纸到代码一步到位

看起来很美好,但Airbnb还没公开该模型中端到端训练的细节,以及手工设计的图像特征对该模型的贡献度。这是该公司特有的闭源解决方案专利,可能不会进行公开。

好在,一个叫Ashwin Kumar的程序员创建了一个开源版本,让开发者/设计师的工作变得更简单。

以下内容翻译自他的博客:

理想上,这个模型可以根据网站设计的简单手绘原型,很快地生成一个可用的HTML网站:

SketchCode模型利用手绘线框图来生成HTML网站

事实上,上面例子就是利用训练好的模型在测试集上生成的一个实际网站,代码请访问:https://github.com/ashnkumar/sketch-code。

从图像标注中获取灵感

目前要解决的问题属于一种更广泛的任务,叫做程序综合(program synthesis),即自动生成工作源代码。尽管很多程序综合研究通过自然语言规范或执行追踪法来生成代码,但在当前任务中,我会充分利用源图像,即给出的手绘线框图来展开工作。

在机器学习中有一个十分热门的研究领域,称为图像标注(image caption),目的是构建一种把图像和文本连接在一起的模型,特别是用于生成源图像内容的描述。

图像标注模型生成源图像的文本描述

我从一篇pix2code论文和另一个应用这种方法的相关项目中获得灵感,决定把我的任务按照图像标注方式来实现,把绘制的网站线框图作为输入图像,并将其相应的HTML代码作为其输出内容。

注:上段提到的两个参考项目分别是

pix2code论文:https://arxiv.org/abs/1705.07962

floydhub教程:https://blog.floydhub.com/turning-design-mockups-into-code-with-deep-learning/?source=techstories.org

获取合适的数据集

确定图像标注方法后,理想中使用的训练数据集会包含成千上万对手绘线框图和对应的HTML输出代码。但是,目前还没有我想要的相关数据集,我只好为这个任务来创建数据集。

最开始,我尝试了pix2code论文给出的开源数据集,该数据集由1750张综合生成网站的截图及其相应源代码组成。

pix2code数据集中的生成网站图片和源代码

这是一个很好的数据集,有几个有趣的地方:

  • 该数据集中的每个生成网站都包含几个简单的辅助程序元素,如按钮、文本框和DIV对象。尽管这意味着这个模型受限于将这些少数元素作为它的输出内容,但是这些元素可通过选择生成网络来修改和扩展。这种方法应该很容易地推广到更大的元素词汇表。

  • 每个样本的源代码都是由领域专用语言(DSL)的令牌组成,这是该论文作者为该任务所创建的。每个令牌对应于HTML和CSS的一个片段,且加入编译器把DSL转换为运行的HTML代码。

彩色网站图像变手绘图

为了修改我的任务数据集,我要让网站图像看起来像手工绘制出的。我尝试使用Python中的OpenCV库和PIL库等工具对每张图像进行修改,包括灰度转换和轮廓检测。

最终,我决定直接修改原始网站的CSS样式表,通过执行以下操作:

1. 更改页面上元素的边框半径来平滑按钮和DIV对象的边缘;

2. 模仿绘制的草图来调整边框的粗细,并添加阴影;

3. 将原有字体更改为类似手写的字体;

最终实现的流程中还增加了一个步骤,通过添加倾斜、移动和旋转来实现图像增强,来模拟实际绘制草图中的变化。

使用图像标注模型架构

现在,我已经处理好数据集,接下来是构建模型。

我利用了图像标注中使用的模型架构,该架构由三个主要部分组成:

1. 一种使用卷积神经网络(CNN)的计算机视觉模型,从源图像提取图像特征;

2. 一种包含门控单元GRU的语言模型,对源代码令牌序列进行编码;

3. 一个解码器模型,也属于GRU单元,把前两个步骤的输出作为输入,并预测序列中的下一个令牌。

以令牌序列为输入来训练模型

为了训练模型,我将源代码拆分为令牌序列。模型的输入为单个部分序列及它的源图像,其标签是文本中的下一个令牌。该模型使用交叉熵函数作为损失函数,将模型的下个预测令牌与实际的下个令牌进行比较。

在模型从头开始生成代码的过程中,该推理方式稍有不同。图像仍然通过CNN网络进行处理,但文本处理开始时仅采用一个启动序列。在每个步骤中,模型对序列中输出的下个预测令牌将会添加到当前输入序列,并作为新的输入序列送到模型中;重复此操作直到模型的预测令牌为,或该过程达到每个文本中令牌数目的预定义值。

当模型生成一组预测令牌后,编译器就会将DSL令牌转换为HTML代码,这些HTML代码可以在任何浏览器中运行。

用BLEU分数评估模型

我决定使用BLEU分数来评估模型。这是机器翻译任务中常用的一种度量标准,通过在给定相同输入的情况下,衡量机器生成的文本与人类可能产生内容的近似程度。

实际上,BLEU通过比较生成文本和参考文本的N元序列,以创建修改后的准确版本。它非常适用于这个项目,因为它会影响生成HTML代码中的实际元素,以及它们之间的相互关系。

最棒的是,我还可以通过检查生成的网站来比较当前的实际BLEU分数。

观察BLEU分数

当BLEU分数为1.0时,则说明给定源图像后该模型能在正确位置设置合适的元素,而较低的BLEU分数这说明模型预测了错误元素或是把它们放在相对不合适的位置。我们最终模型在评估数据集上的BLEU分数为0.76。

福利:定制网页风格

后来,我还想到,由于该模型只生成当前页面的框架,即文本的令牌,因此我可以在编译过程中添加一个定制的CSS层,并立刻得到不同风格的生成网站。

一个手绘图生成多种风格的网页

把风格定制和模型生成两个过程分开,在使用模型时带来了很多好处:

1.如果想要将SketchCode模型应用到自己公司的产品中,前端工程师可以直接使用该模型,只需更改一个CSS文件来匹配该公司的网页设计风格;

2. 该模型内置的可扩展性,即通过单一源图像,模型可以迅速编译出多种不同的预定义风格,因此用户可以设想出多种可能的网站风格,并在浏览器中浏览这些生成网页。

总结和展望

受到图像标注研究的启发,SketchCode模型能够在几秒钟内将手绘网站线框图转换为可用的HTML网站。

但是,该模型还存在一些问题,这也是我接下来可能的工作方向:

1. 由于这个模型只使用了16个元素进行训练,所以它不能预测这些数据以外的令牌。下一步方向可能是使用更多元素来生成更多的网站样本,包括网站图片,下拉菜单和窗体,可参考启动程序组件(https://getbootstrap.com/docs/4.0/components/buttons/)来获得思路;

2. 在实际网站构建中,存在很多变化。创建一个能更好反映这种变化的训练集,是提高生成效果的一种好方法,可以通过获取更多网站的HTML/CSS代码以及内容截图来提高;

3. 手绘图纸也存在很多CSS修改技巧无法捕捉到的变化。解决这个问题的一种好方法是使用生成对抗网络GAN来创建更逼真的绘制网站图像。

相关地址

代码:https://github.com/ashnkumar/sketch-code

原文:https://blog.insightdatascience.com/automated-front-end-development-using-deep-learning-3169dd086e82

— 完 —

诚挚招聘

量子位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在量子位公众号(QbitAI)对话界面,回复“招聘”两个字。

量子位 QbitAI · 头条号签约作者

վ'ᴗ' ի 追踪AI技术和产品新动态

建Web游戏



今天小编教大家如何用Python编程语言创建Web游戏,如果你能完成,你就可以算是一个能力相当不错的Python初学者了。虽然还需要多读一些书,多写一些程序,不过你已经具备进一步学习的功底了。接下来的学习就只是时间、动力及资源的问题了。

在这个习题中,我们不会去创建一个完整的游戏,相反,我们会为习题42中的游戏创建一个“引擎”(engine),让这个游戏能够在浏览器中运行起来。这会涉及重构习题42中的游戏,混合习题47中的结构,添加自动测试代码,最后创建一个可以运行这个游戏的Web引擎。

这是一个很庞大的习题。预计你要花一周到一个月才能完成。最好的方法是一点一点来,每晚完成一点,在进行下一步之前确认上一步已经正确完成。

重构习题43中的游戏

你已经在两个习题中修改了gothonweb项目,这个习题中会再修改一次。你学习的这种修改的技术叫做“重构”,或者用我喜欢的讲法来说,叫“修理”。重构是一个编程术语,它指的是清理旧代码或者为旧代码添加新功能的过程。你其实已经做过这样的事情了,只不过不知道这个术语而已。重构是软件开发中经历的最习以为常的事情。

在这个习题中你要做的是将习题47中的可以测试的房间地图和习题43中的游戏这两样东西合并到一起,创建一个新的游戏结构。游戏的内容不会发生变化,只不过我们会通过“重构”让它有一个更好的结构而已。

第一步是将ex47/game.py的内容复制到gothonweb/map.py中,然后将tests/ex47_tests.py的内容复制到tests/map_tests.py中,然后再次运行nosetests,确认它们还能正常工作。

注意

从现在开始,我不会再展示运行测试的输出了,我假设你会回去运行这些测试,而且知道什么样的输出是正确的。

将习题47的代码复制完毕后,就该开始重构它,让它包含习题43中的地图。我一开始会把基本结构为你准备好,然后你需要去完成map.py和map_tests.py里边的内容。

首先要做的是用Room这个类来构建地图的基本结构。

map.py

1  class Room(object):
2  
3      def __init__(self, name, description):
4          self.name = name
5          self.description = description
6          self.paths = []
7  
8      def go(self, direction):
9           return self.paths.get(direction, None)
10  
11      def add_paths(self, paths):
12           self.paths.update(paths)
13  
14  
15  central_corridor = Room("Central Corridor",
16  """
17  The Gothons of Planet Percal #25 have invaded your ship and destroyed
18  your entire crew.  You are the last surviving member and your last
19  mission is to get the neutron destruct bomb from the Weapons Armory,
20  put it in the bridge, and blow the ship up after getting into an 
21  escape pod.
22  
23  You're running down the central corridor to the Weapons Armory when
24  a Gothon jumps out, red scaly skin, dark grimy teeth, and evil clown costume
25  flowing around his hate filled body.  He's blocking the door to the
26  Armory and about to pull a weapon to blast you.
27  """)
28  
29  
30  laser_weapon_armory = Room("Laser Weapon Armory",
31  """
32  Lucky for you they made you learn Gothon insults in the academy.
33  You tell the one Gothon joke you know:
34  Lbhe zbgure vf fb sng, jura fur fvgf nebhaq gur ubhfr, fur fvgf nebhaq gur ubhfr.
35  The Gothon stops, tries not to laugh, then busts out laughing and can't move.
36  While he's laughing you run up and shoot him square in the head
37  putting him down, then jump through the Weapon Armory door.
38  
39  You do a dive roll into the Weapon Armory, crouch and scan the room
40  for more Gothons that might be hiding.  It's dead quiet, too quiet.
41  You stand up and run to the far side of the room and find the
42  neutron bomb in its container.  There's a keypad lock on the box
43  and you need the code to get the bomb out.  If you get the code
44  wrong 10 times then the lock closes forever and you can't
45  get the bomb.  The code is 3 digits.
46  """)
47  
48  
49  the_bridge = Room("The Bridge",
50  """
51  The container clicks open and the seal breaks, letting gas out.
52  You grab the neutron bomb and run as fast as you can to the
53  bridge where you must place it in the right spot.
54  
55  You burst onto the Bridge with the netron destruct bomb
56  under your arm and surprise 5 Gothons who are trying to
57  take control of the ship.  Each of them has an even uglier
58  clown costume than the last.  They haven't pulled their
59  weapons out yet, as they see the active bomb under your
60  arm and don't want to set it off.
61  """)
62  
63  
64  escape_pod = Room("Escape Pod",
65  """
66  You point your blaster at the bomb under your arm
67  and the Gothons put their hands up and start to sweat.
68  You inch backward to the door, open it, and then carefully
69  place the bomb on the floor, pointing your blaster at it.
70  You then jump back through the door, punch the close button
71  and blast the lock so the Gothons can't get out.
72  Now that the bomb is placed you run to the escape pod to
73  get off this tin can.
74  
75  You rush through the ship desperately trying to make it to
76  the escape pod before the whole ship explodes.  It seems like
77  hardly any Gothons are on the ship, so your run is clear of
78  interference.  You get to the chamber with the escape pods, and
79  now need to pick one to take.  Some of them could be damaged
80  but you don't have time to look.  There's 5 pods, which one
81  do you take?
82  """)
83  
84  
85  the_end_winner = Room("The End",
86  """
87  You jump into pod 2 and hit the eject button.
88  The pod easily slides out into space heading to
89  the planet below.  As it flies to the planet, you look
90  back and see your ship implode then explode like a
91  bright star, taking out the Gothon ship at the same
92  time.  You won!
93  """)
94  
95  
96  the_end_loser = Room("The End",
97  """
98  You jump into a random pod and hit the eject button.
99  The pod escapes out into the void of space, then
100  implodes as the hull ruptures, crushing your body
101  into jam jelly.
102  """
103  )
104  
105  escape_pod.add_paths({
106      '2': the_end_winner,
107      '*': the_end_loser
108  })
109  
110  generic_death = Room("death", "You died.")
111  
112  the_bridge.add_paths({
113      'throw the bomb': generic_death,
114      'slowly place the bomb': escape_pod
115  })
116  
117  laser_weapon_armory.add_paths({
118      '0132': the_bridge,
119      '*': generic_death
120  })
121  
122  central_corridor.add_paths({
123      'shoot!': generic_death,
124      'dodge!': generic_death,
125      'tell a joke': laser_weapon_armory
126  })
127  
128  START = central_corridor

你会发现Room类和地图有一些问题。

1.我们必须把以前放在if-else结构中的房间描述做成每个房间的一部分。这样房间的次序就不会被打乱了,这对我们的游戏是一件好事。这是你后面要修改的东西。

2.原版游戏中我们使用了专门的代码来生成一些内容,如炸弹的激活键码、舰舱的选择等,这次我们做游戏时就先使用默认值好了,不过后面的附加练习里,我会要求你把这些功能再加到游戏中。

3.我为游戏中所有错误决策的失败结尾写了一个generic_death,你需要去补全这个函数。你需要把原版游戏中所有的场景结局都加进去,并确保代码能正确运行。

4.我添加了一种新的转换模式,以"*"为标记,用来在游戏引擎中实现“捕获所有操作”的功能。

等把上面的代码基本写好以后,接下来就是你必须继续写的自动测试tests/map_test.py了。

map_tests.py

1  from nose.tools import *
2  from gothonweb.map import *
3  
4  def test_room():
5       gold = Room("GoldRoom", 
6                      """This room has gold in it you can grab. There's a
7                      door to the north.""")
8       assert_equal(gold.name, "GoldRoom")
9       assert_equal(gold.paths, {})
10  
11  def test_room_paths():
12      center = Room("Center", "Test room in the center.")
13      north = Room("North", "Test room in the north.")
14      south = Room("South", "Test room in the south.")
15  
16      center.add_paths({'north': north, 'south': south})
17      assert_equal(center.go('north'), north)
18      assert_equal(center.go('south'), south)
19  
20  def test_map():
21      start = Room("Start", "You can go west and down a hole.")
22      west = Room("Trees", "There are trees here, you can go east.")
23      down = Room("Dungeon", "It's dark down here, you can go up.")
24  
25      start.add_paths({'west': west, 'down': down})
26      west.add_paths({'east': start})
27      down.add_paths({'up': start})
28  
29      assert_equal(start.go('west'), west)
30      assert_equal(start.go('west').go('east'), start)
31      assert_equal(start.go('down').go('up'), start)
32  
33  def test_gothon_game_map():
34      assert_equal(START.go('shoot!'), generic_death)
35      assert_equal(START.go('dodge!'), generic_death)
36  
37      room = START.go('tell a joke')
38      assert_equal(room, laser_weapon_armory)

你在这个习题中的任务是完成地图,并且让自动测试可以完整地检查整个地图。这包括将所有的generic_death对象修正为游戏中实际的失败结尾。让你的代码成功运行起来,并让你的测试越全面越好。后面我们会对地图做一些修改,到时候这些测试将用来确保修改后的代码还可以正常工作。

会话和用户跟踪

在Web应用程序运行的某个位置,你需要追踪一些信息,并将这些信息和用户的浏览器关联起来。在HTTP协议的框架中,Web环境是“无状态”的,这意味着你的每一次请求和你的其他请求都是相互独立的。如果你请求了页面A,输入了一些数据,然后点了一个页面B的链接,那你发送给页面A的数据就全部消失了。

解决这个问题的方法是为Web应用程序建立一个很小的数据存储,给每个浏览器进程赋予一个独一无二的数字,用来跟踪浏览器所做的事情。这个存储通常用数据库或者存储在磁盘上的文件来实现。在lpthw.web这个小框架中实现这样的功能是很容易的,下面就是一个这样的例子。

session.sample.py

1  import web
2  
3  web.config.debug = False
4  
5  urls = (
6        "/count", "count",
7        "/reset", "reset"
8  )
9  app = web.application(urls, locals())
10  store = web.session.DiskStore('sessions')
11  session = web.session.Session(app, store, initializer=['count': 0])
12  
13  class count:
14       def GET(self):
15            session.count += 1
16            return str(session.count)
17  
18  class reset:
19       def GET(self):
20            session.kill()
21            return ""
22  
23  if __name__ == "__main__":
24      app.run()

为了实现这个功能,需要创建一个sessions/文件夹作为程序的会话存储位置,创建好以后运行这个程序,然后检查/count页面,刷新一下这个页面,看计数会不会累加上去。关掉浏览器后,程序就会“忘掉”之前的位置,这也是我们的游戏所需的功能。有一种方法可以让浏览器永远记住一些信息,不过这会让测试和开发变得更难。如果你回到/reset页面,然后再访问/count页面,你可以看到你的计数器被重置了,因为你已经关掉了这个会话。

你需要花点时间弄懂这段代码,注意会话开始时count的值是如何设为0的,另外再看看sessions/下面的文件,看能不能打开。下面是我打开一个Python会话并解码的过程:

>>> import pickle
>>> import base64
>>> base64.b64decode(open("sessions/XXXXX").read())
"(dp1\nS'count'\np2\nI1\nsS'ip'\np3\nV127.0.0.1\np4\nsS'session_id'\np5\nS'XXXX'\np6\ns."
>>>
>>> x = base64.b64decode(open("sessions/XXXXX").read())
>>>
>>> pickle.loads(x)
{'count': 1, 'ip': u'127.0.0.1', 'session_id': 'XXXXX'}

所以,会话其实就是使用pickle和base64这些库写到磁盘上的字典。存储和管理会话的方法很多,大概和Python的Web框架那么多,所以了解它们的工作原理并不是很重要。当然如果你需要调试或者清空会话,知道点儿原理还是有用的。

创建引擎

你应该已经写好了游戏地图和它的单元测试代码。现在要你制作一个简单的游戏引擎,用来让游戏中的各个房间运转起来,从玩家收集输入,并且记住玩家所在的位置。我们将用到你刚学过的会话来制作一个简单的引擎,让它可以:

1.为新用户启动新的游戏;

2.将房间展示给用户;

3.接收用户的输入;

4.在游戏中处理用户的输入;

5.显示游戏的结果,继续游戏,直到玩家角色死亡为止。

为了创建这个引擎,你需要将bin/app.py搬过来,创建一个功能完备的、基于会话的游戏引擎。这里的难点是,我会先使用基本的HTML文件创建一个非常简单的版本,接下来将由你完成它。基本的引擎是下面这个样子的:

app.py

1  import web
2  from gothonweb import map
3  
4  urls = (
5      '/game', 'GameEngine',
6      '/', 'Index',
7  )
8  
9  app = web.application(urls, globals())
10  
11  # little hack so that debug mode works with sessions
12  if web.config.get('_session') is None:
13        store = web.session.DiskStore('sessions')
14        session = web.session.Session(app, store,
15                                            initializer=['room': None])
16      web.config._session = session
17  else:
18       session = web.config._session
19  
20  render = web.template.render('templates/', base="layout")
21  
22  
23  class Index(object):
24       def GET(self):
25          # this is used to "setup" the session with starting values
26          session.room = map.START
27          web.seeother("/game")
28  
29  
30  class GameEngine(object):
31  
32      def GET(self):
33           if session.room:
34                return render.show_room(room=session.room)
35           else:
36              # why is there here? do you need it?
37              return render.you_died()
38  
39      def POST(self):
40           form = web.input(action=None)
41  
42          # there is a bug here, can you fix it?
43          if session.room and form.action:
44               session.room = session.room.go(form.action)
45  
46          web.seeother("/game")
47  
48  if __name__ == "__main__":
49      app.run()

在这个脚本里你可以看到更多的新东西,不过了不起的事情是,整个基于网页的游戏引擎只要一个小文件就可以做到了。这段脚本里最有技术含量的就是将会话带回来的那几行,这对于调试模式下的代码重载是必需的,否则每次刷新网页,会话就会消失,游戏也不会再继续了。

在运行bin/app.py之前,你需要修改PYTHONPATH环境变量。不知道什么是环境变量?要运行一个最基本的Python程序,你就得学会环境变量,用Python的人就喜欢这样:

在终端输入下面的内容:

export PYTHONPATH=$PYTHONPATH:.

如果用的是Windows,那就在PowerShell中输入以下内容:

$env:PYTHONPATH = "$env:PYTHONPATH;."

你只要针对每一个shell会话输入一次就可以了,不过如果你运行Python代码时看到了导入错误,那就需要去执行一下上面的命令,或者是因为你上次执行的有错才导致导入错误的。

接下来需要删掉templates/hello_form.html和templates/index.html,然后重新创建上面代码中提到的两个模板。下面是一个非常简单的templates/show_room.html,供你参考。

show_room.html

$def with (room)

<h1> $room.name </h1>

<pre>
$room.description
</pre>

$if room.name == "death":
    <p><a href="/">Play Again?</a></p>
$else:
    <p>
    <form action="/game" method="POST">
        - <input type="text" name="action"> <input type="SUBMIT">
    </form>
    </p>

这就用来显示游戏中的房间的模板。接下来,你需要在用户跑到地图的边界时,用一个模板告诉用户,他的角色的死亡信息,也就是templates/you_died.html这个模板。

you_died.html

<h1>You Died!</h1>

<p>Looks like you bit the dust.</p>
<p><a href="/">Play Again</a></p>

准备好这些文件就可以做下面的事情了。

1.再次运行测试代码tests/app_tests.py,这样就可以测试这个游戏。由于会话的存在,你可能顶多只能实现几次点击,不过你应该可以做出一些基本的测试来。

2.删除sessions/*下的文件,再重新运行一遍游戏,确认游戏是从一开始运行的。

3. 运行python bin/app.py脚本,试玩一下你的游戏。

你需要和往常一样刷新和修正你的游戏,慢慢修改游戏的HTML文件和引擎,直到实现游戏需要的所有功能为止。

期末考试

你有没有觉得我一下子给了你超多的信息呢?那就对了,我想要你在学习技能的同时有一些可以用来鼓捣的东西。为了完成这个习题,我将给你最后一套需要你自己完成的练习。你会注意到,到目前为止你写的游戏并不是很好,这只是你的第一版代码而已,你现在的任务就是让游戏更加完善,实现下面的这些功能。

1.修正代码中所有我提到和没提到的bug,如果你发现了新bug,你可以告诉我。

2.改进所有的自动测试,以便可以测试更多的内容,直到你可以不用浏览器就能测到所有的内容为止。

3.让HTML页面看上去更美观一些。

4.研究一下网页登录系统,为这个程序创建一个登录界面,这样人们就可以登录这个游戏,并且可以保存游戏高分。

5.完成游戏地图,尽可能地把游戏做大,功能做全。

6.给用户一个“帮助系统”,让他们可以查询每个房间里可以执行哪些命令。

7.为游戏添加新功能,想到什么功能就添加什么功能。

8.创建多个地图,让用户可以选择他们想要玩的一张地图来进行游戏。你的bin/app.py应该可以运行提供给它的任意地图,这样你的引擎就可以支持多个不同的游戏。

9.最后,使用在习题48和习题49中学到的东西创建一个更好的输入处理器。你手头已经有了大部分必要的代码,只需要改进语法,让它和你的输入表单以及游戏引擎挂钩即可。

祝你好运!

常见问题回答

我在游戏中用了会话(`session)`,不能用nosetests测试。

你需要阅读并了解带reloader的会话:http://webpy.org/cookbook/session_with_reloader。

我看到了ImportError。

错误路径,错误Python版本,PYTHONPATH没设置对,漏了__init__.py文件,拼写错误,都检查一下吧。

有天老板找我到办公室跟我说要做一个商城,商城卖出去东西就有佣金可以拿。我听着就头大。老板打开电脑给我看了网站:你看一下这个网站,照着它的流程就可以拥有一个商城了。我靠过去一看,大概了解一下:原来是利用第三方工具就可以构建一个导购网站,只要消费者在网站领取优惠券就会自动跳转到某bao的购买页面,购买成功后就可以有佣金了。我看了一下觉得可以,只要不让我敲代码一切好说。于是我照着流程构建了一个网站,然后勾选了很多零食进行推广,然后我就发现了一个问题:我只勾选了一些零食啊,商城怎么还有其它类型的商品?我思索了一下就明白了,这网站还是挺流氓的,还掺杂着其他人的推广链接,我一想这样不行,转化率肯定低啊。果不其然,试用了一天就只有5个单子,因为公司的网站还是挺有流量的,所以这转化率不可能这么低。老板看了一下,觉得没什么用让我把商城入口给关了,我只好照做,但是我心里对这流氓网站不服啊,于是我打算自己做一个导购网站。(最后还是要敲代码(。・_・)/~~~)

想法

  1. 在推广平台上下载一个商品清单的excel文档,文档的内容包含:商品的名称、商品的主图链接、商品分类、商品价格、商品推广链接.....。
  2. 利用python读取excel,获得分类、商品信息的json文件。
  3. 创建一个html页面读取json文件,把分类和商品显示出来,利用html中的锚点定位,点击就会滚动到对应的分类商品,就可以选择心仪的商品下单,从而达到推广商品的作用了。

行动

1. 安装xlrd

cmd窗口: pip install xlrd

2.创建index.py,导入模块

import xlrd

3.打开Excel文件读取数据

wb= xlrd.open_workbook('文件路径')

4.获取表格

sheet1 = wb.sheet_by_index(0) #这里的excel文档内只有一个表格,0代表第一个

5.获取表格的行数

rows = sheet1.nrows

6.获取表格中的类目

商品一级类目

住宅家具

影音电器

影音电器

美容护肤

厨房电器

运动服/休闲服装

餐饮具

category0 = sheet1.col_values(4) #获取列内容(类目),这里excel文档的第四列是类目
del category0[0] #删除列表中的 "商品一级类目"
category = sorted(set(category0),key=category0.index) #类目列表->去除重复

7.整理数据

[ 
 [
 分类名,
 [商品信息]
 ],
 [
 分类名,
 [商品信息]
 ]
]
data = []
for i,v in enumerate(category):
 data.append([v,[]])
for i,v in enumerate(data):
 for x in range(rows):
 if v[0] == sheet1.cell(x,4).value:
 data[i][1].append(sheet1.row_values(x))

8.导出json文件

jsonData = json.dumps(data, ensure_ascii=False)
with open('results.json', 'w',encoding="utf-8") as f:
 f.write(jsonData)

9.运行index.py,获得json文件

cmd窗口:python index.py

10.创建html页面,并引用json文件

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
 <script>//引用jquery
 $(function(){
function color16(){//十六进制颜色随机
 var r = Math.floor(Math.random()*256);
 var g = Math.floor(Math.random()*256);
 var b = Math.floor(Math.random()*256);
 var color = '#'+r.toString(16)+g.toString(16)+b.toString(16);
 return color;
 }
var navo = '';//类目导航
var info = '';//商品内容
$.get('./results.json', function(data) {//请求json文件
 
 $.each(data, function(index, val) {
 navo+="<a href='#"+val[0]+"''>"+val[0]+"</a> "
 });//把json文件中的类目数组遍历出来,并用锚定位
 $.each(data, function(index, val) {
 var div_title = "<div id='"+val[0]+"' style='float:left;'>";
 var div_content = "";
 $.each(val[1], function(index, val) {
 div_content+="<div style='background:"+color16()+"' onclick=\"location.href=\'"+val[21]+"\'\" class='pro_img'>"+val[1]+"<span class='money'>¥"+val[6]+"</span><\/div>"
 });
 var div_footer ="</div><br>";
 info+=div_title+div_content+div_footer
 });
$('#nav').html(navo);//把导航显示出来
$('#content').html(info);//把商品显示出来
 },'json');
 })
 
 </script>
 <style>
 #content{
 margin-top: 10px
 }
 .money{
 position: absolute;
 left: 0;
 bottom: 0;
 height: 30px;
 line-height: 30px;
 color: #e22a40;
 font-weight: 700
 }
 .pro_img{
 position: relative;
 float: left;
 width: 220px;
 height: 220px;
 line-height: 220px;
 text-align: center;
 border: 1px solid #eee;
 cursor: pointer;
 font-size: 30px;
 white-space:normal; 
 overflow:hidden; /*超过部分不显示*/
      text-overflow:ellipsis; /*超过部分用点点表示*/
      white-space:nowrap;*//*不换行
 }
 </style>
</head>
<body>
<div id="nav"></div>
<div id="content"></div>
</body>
</html>

效果

https://fjxasdf.github.io/daogou (github比较卡)

遗留问题

  1. 导购页面没有样式,不够美观。
  2. 没有显示商品图片,由于excel文档中有1万条商品信息,把一万张图片显示出来太卡了。