根据输入的姓氏,随机获取对应的男生和女生姓名

项目介绍

数据与编程之美

为什么会有这么”奇葩“的项目呢?

这个想法的出现来源于,我之前想过要写一篇小说,但是苦于思考创造人名这一步,所以就在想能不能有一个网站,我只需要输入姓氏,就能得到一些人名?所以就有了这个项目。

哪里来的数据呢?

这个问题,其实也不难解决,百度搜一会就有答案啦。于是我找到了姓名大全这个网站,右击给这个网站做了一个“检查”,发现这个网站的构造挺简单的,不需要太高深的爬虫技巧就可以获取到数据。再把数据存到sqlite3数据库中,作为这个项目的源数据。

这个项目会很复杂吗?

越学越简单,越简单越难。

任何项目都可以很复杂的,所以以我的能力,尽可能的去实现较“复杂”的操作。

其实,乍一看,这个项目不复杂:

  1. 爬虫获取数据,并保存到数据库。
  2. 后端从数据获取数据,发送到前端。
  3. 前端一个搜索框,根据输入的姓氏,从后端得到姓名并展示。

我在这个项目中主要用到的是这些技术:requests,flask,vue。有些知识我也不是很懂,都是直接一边学一边用,我觉得学以致用,更能感受到学习的乐趣。

源代码获取

公众号“帅帅的Python”,回复“姓名”即可获取源码。

数据获取

网站分析

需要从姓名大全这个网站获取的数据有:

  1. 百家姓列表:[‘赵’,‘钱’,‘孙’,‘李’,…]
  2. 每个姓对应的男生名、女生名

获取一个百家姓列表,主要是为了判断前端输入的姓氏是否正确。

一、在谷歌浏览器中,右击“检查”,通过一番分析后,找到一个a标签,如下形式,可以通过这个标签,获取到该姓氏的具体的超链接(通过href获取),以及百家姓列表(通过文本拆分获取)。

<a class="btn btn2" href="//zhao.resgain.net/name_list.html" title="赵姓名字大全共有赵姓名字162998个">赵姓名字大全</a>

二、继续分析,点击超链接进入到姓氏的详情页,发现每个姓氏都有男生和女生的超链接,如下:

# 男生网站为:http://zhao.resgain.net/name/boys.html
# 女生网站为:http://zhao.resgain.net/name/girls.html

与之前分析的a标签中的href中的超链接有所不同,所以需要将**/name_list.html替换为/name/boys.html/name/girls.html**才能得到对应姓氏的男生、女生姓名地址。

三、进入男生姓名网站中,每个姓名的html代码如下:

<a href="#" class="livemsg" data-name="赵竹林"><span class="glyphicon glyphicon-comment"></span></a>			  		

通过class="livemsg"定位到该a标签,然后获取到data-name的属性值即可得到姓名。

数据库设计

这里选择sqlite3为数据库,主要是因为不需要复杂的配置,操作简单,轻便小巧。

这里只需要存姓名(NAME)、性别(SEX)两个字段即可,当然,再加一个自增的ID字段。

数据库设计代码如下:

def create_db():
    conn = sqlite3.connect(r"tools_app.db")
    cursor = conn.cursor()
    cursor.execute('''CREATE TABLE RANDOM_NAME
           (ID INTEGER PRIMARY KEY  AUTOINCREMENT,
           NAME           TEXT    NOT NULL,
           SEX         TEXT    NOT NULL);''')
    print("RANDOM_NAME created successfully")
    conn.commit()
    conn.close()

代码解释:

1、conn是一个sqlite3数据库对象,只需传入database参数,这里传的tools_app.db表示,连接的是同级目录下的名为tools_app.db的数据库(数据库不需要提前创建)

2、cursor为操作数据库的“游标”,可以把数据库比喻成一个仓库,游标就像是一个优秀的员工,cursor.execute()就是这个员工需要对这个仓库做的事情;conn.commit()就是员工操作完后,仓库将操作的结果告诉员工。conn.close()仓库关门,期待下一次的开启。

这个函数运行完之后,会在同级目录下创建一个名为tools_app.db的数据库,该数据库中有一个名为RANDON_NAME的表。

爬虫设计

通过之前的网站分析,爬虫的运行应该分为以下三步:

  1. 通过姓名大全地址获取到每个姓氏href和文本信息。
  2. 通过第一步获取的href,获取男生和女生的网站。
  3. 通过第二步获取的网站获取对应的姓名。

第一步,代码详解:

def get_name_link():
    url = "http://www.resgain.net/xmdq.html"
    res = requests.get(url)
    soup = BeautifulSoup(res.text, 'lxml')
    name_links = []
    for s in soup.find_all(attrs={'class': 'btn btn2'}):
        name_links.append("http:" + s.get('href'))
    return name_links

代码解释:

1、通过request.get()方法,传入url就可以获取到对应的网页对象,使用res.text属性获取到网页源码

2、使用BeautifulSoup格式化网页,可以很方便,很快速的定位到需要的数据。

3、soup.find_all(attrs={‘class’: ‘btn btn2’}),通过class属性为btn btn2找到所有符合条件的元素,返回的是一个列表

4、通过s.get(‘href’)方法可以获取到标签元素的href属性的值

同理,可以获取到百家姓列表:

def get_name_list():
    url = "http://www.resgain.net/xmdq.html"
    res = requests.get(url)
    soup = BeautifulSoup(res.text, 'lxml')
    name_list = []
    for s in soup.find_all(attrs={'class': 'btn btn2'}):
        name_list.append(s.text.split("姓")[0])
    return name_list

1、s.text可以获取该标签中的文本内容

2、split(“姓”)方法,按照"姓"分隔字符串,结果为一个列表,选择第一个即为百家姓的姓氏。例如:"赵姓名字大全"分隔后变成:[“赵”,“姓名字大全”]

第二步,构造男生和女生的详情链接:

name_link_list = get_name_link()
for name_link in name_link_list:
    url_boys = name_link.replace("/name_list.html", "/name/boys.html")
    url_girls = name_link.replace("/name_list.html", "/name/girls.html")

1、字符串替换用replace(需要替换的内容,替换的内容)

第三步,通过构造的链接获取姓名:

def get_data(url):
    con = sqlite3.connect(r'tools_app.db')
    cursor = con.cursor()
    res = requests.get(url)
    soup = BeautifulSoup(res.text, 'lxml')
    if "boy" in url:
        sex_ = "男"
    else:
        sex_ = "女"
    for s in soup.find_all(attrs={'class': 'btn btn-default btn-lg namelist'}):
        name = s.find(attrs={'class': 'livemsg'}).get('data-name')
        sql = "insert into RANDOM_NAME (name,sex) values('{0}','{1}');".format(name,sex_)
        cursor.execute(sql)
        con.commit()
    print(url, "完成")
    con.close()

1、同样连接tools_app.db数据库,创建一个cursor对象,用来操作数据库

2、同样,使用soup.find_all()获取到所有的标签,通过get方法获取到姓名数据

3、构造sql语句,用format()方法格式化字符串

4、cursor.execute()执行sql插入语句,conn.commit()提交记录,最后for循环结束后,conn.close()关闭连接

开始运行:

if __name__ == '__main__':
    # 创建数据库和RANDOM_NAME表
    # print(get_name_list())
    # create_db()
    # get_data('http://zhang.resgain.net/name/boys.html')
    name_link_list = get_name_link()
    for name_link in name_link_list:
        url_boys = name_link.replace("/name_list.html", "/name/boys.html")
        url_girls = name_link.replace("/name_list.html", "/name/girls.html")
        get_data(url_boys)
        t = random.randint(1, 3)
        time.sleep(t)
        get_data(url_girls)
        t = random.randint(1, 3)
        time.sleep(t)

爬虫获取数据总计耗时2小时左右,获取到数据249298条。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RO6i5PpF-1617715189324)(./pictures/random_names/爬虫数据展示.png)]

前端设计

采用Bootstrap4前端框架、vue2.js框架、vue-ajax-axios异步加载框架。

页面构思

  1. 需要一个简单的搜索框,还需要两个列表组来展示搜索的男生和女生姓名。
  2. 第一次进入,会有默认的页面,默认加载郑姓的数据。
  3. 在前端这里判断输入的姓氏,是否在百家姓里面,如果不在,将确定按钮改为红色“禁用”。
  4. 输入正确的姓氏后,每次点击确定按钮,展示的姓名都是随机的。

确定页面展示:
确定页面

禁用页面展示:
前端错误页面

代码分析

卡片

Bootstrap4-卡片

将页面卡片化,会感觉更加的清晰明了,只需要将输入框放到card-header,列表组放到card-body中即可。

<div class="card">
  <div class="card-header">
    Featured
  </div>
  <div class="card-body">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
</div>

输入框

Bootstrap4-输入组地址

这里有许多漂亮的输入框,选择一个简单的即可。

<div class="input-group mb-3">
  <input type="text" class="form-control" placeholder="Recipient's username" aria-label="Recipient's username" aria-describedby="button-addon2">
  <div class="input-group-append">
    <button class="btn btn-outline-secondary" type="button" id="button-addon2">Button</button>
  </div>
</div>

当然,直接拿来用是不行的,需要修改一番。使用vue框架的v-if来判断输入的姓氏是否正确,用v-on:click来触发点击事件。

1、判断一个元素在不在一个数组中,js的判断方法为:indexOf(),结果为-1表示不在数组中。

2、v-if和v-else所在的标签也紧挨着,才能生效。

修改代码如下:

<div class="input-group">
    <input type="text" class="form-control" v-model="input_name" placeholder="您的姓氏">
    <div class="input-group-append">
        <button class="btn btn-primary" v-if="name_index >= 0" v-on:click="get_info">
            确定
        </button>
        <button class="btn btn-danger" v-else>错误</button>
    </div>
</div>

列表组

Bootstrap4-列表组地址

这里涉及到男生和女生,所以选择将两个列表组,并排放置在同一行中。

<div class="row">
    <div class="col-sm-6">
        <ul class="list-group">
            <button type="button" class="list-group-item list-group-item-action active"
                    aria-current="true">
                <b>男生姓名:</b>
            </button>
            <button class="list-group-item list-group-item-action text-center"
                    v-for="name in names['boy_names']">{[ name ]}
            </button>
        </ul>
    </div>
    <div class="col-sm-6">
        <ul class="list-group">
            <button type="button" class="list-group-item list-group-item-action active"
                    aria-current="true">
                <b>女生姓名:</b>
            </button>
            <button class="list-group-item list-group-item-action text-center"
                    v-for="name in names['girl_names']">{[ name ]}
            </button>
        </ul>
    </div>
</div>

JavaScript

使用vue的语法

1、由于vue的模板是{{}},与flask中内置的jinja2语法有冲突,所以需要用delimiters参数将vue的模板改为[{}]。

2、data:input_name为默认的姓氏;names中包含默认的男生和女生姓名;right_first_name为正确的百家姓氏。

3、name_index:默认为1,表示input_name在right_first_name中。

4、watch:监听input_name的值是否改变,函数的nval参数为新输入的姓氏。

5、methods:用ajax从后端的get_names路由获取对应的数据。

vm = new Vue({
        el: "#rn_app",
        delimiters: ['{[', ']}'],
        data: function () {
            return {
                "input_name": "郑",
                "names": {
                    "boy_names": ['郑宇坤', '郑鑫黎', '郑闰', '郑文锶', '郑小垚'],
                    "girl_names": ['郑琪', '郑雯欣', '郑丽萍', '郑伊春', '郑蘅'],
                },
                "right_first_name": ['赵', '钱', '孙', '李', '周', '吴', '郑', '王',......],
                "name_index": 1
            }
        },
        watch: {
            input_name: function (nval) {
                this.name_index = this.right_first_name.indexOf(nval);
            }
        },
        methods: {
            get_info: function () {
                axios.get("/get_names", {params: {"input_name": this.input_name}})
                    .then(response => (this.names = response.data.names))
                    .catch(function (error) {
                        console.log(error)
                    })
            }
        }
    })

后端

根据前端的分析,后端需要2个接口,index和get_names接口,index负责展示页面,get_names返回查询的姓名。

index接口

@app.route("/")
def index():
    return render_template("random_name.html")

get_names接口

1、数据库配置为当前路径下的/spiders/tools_app.db。

2、使用SQL语句随机获取数据。

3、用jsonify将数据封装为json数返回。

@app.route("/get_names", methods=["GET", "POST"])
def get_names():
    con = sqlite3.connect(r"./spiders/tools_app.db")
    cursor = con.cursor()
    input_name = request.args.get("input_name", "None")
    # 根据input_name从数据库随机获取5个男生姓名,和5个女生姓名
    boy_sql = f"select name from RANDOM_NAME where NAME like '{input_name}%' and SEX='男' order by random()limit 5"
    girl_sql = f"select name from RANDOM_NAME where NAME like '{input_name}%' and SEX='女' order by random()limit 5"
    cursor.execute(boy_sql)
    con.commit()
    boy_names_list = [i[0] for i in cursor.fetchall()]
    cursor.execute(girl_sql)
    con.commit()
    girl_names_list = [i[0] for i in cursor.fetchall()]
    con.close()
    return jsonify({"names": {"boy_names": boy_names_list, "girl_names": girl_names_list}})

启动

本地启动,端口为5000端口,启动后访问http://localhost:5000/,即可看到页面。

if __name__ == '__main__':
    app.run(host="localhost", port=5000, debug=True)

源代码获取

公众号“帅帅的Python”,回复“姓名”即可获取源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅帅的Python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值