源代碼獲取
微信公眾號《帥帥的Python》,回復(fù)《姓名》即可獲取源碼。
項目介紹
為什么會有這么”奇葩“的項目呢?
這個想法的出現(xiàn)來源于,我之前想過要寫一篇小說,但是苦于思考創(chuàng)造人名這一步,所以就在想能不能有一個網(wǎng)站,我只需要輸入姓氏,就能得到一些人名?所以就有了這個項目。
哪里來的數(shù)據(jù)呢?
這個問題,其實也不難解決,百度搜一會就有答案啦。于是我找到了姓名大全這個網(wǎng)站,右擊給這個網(wǎng)站做了一個“檢查”,發(fā)現(xiàn)這個網(wǎng)站的構(gòu)造挺簡單的,不需要太高深的爬蟲技巧就可以獲取到數(shù)據(jù)。再把數(shù)據(jù)存到sqlite3數(shù)據(jù)庫中,作為這個項目的源數(shù)據(jù)。
這個項目會很復(fù)雜嗎?
越學(xué)越簡單,越簡單越難。
任何項目都可以很復(fù)雜的,所以以我的能力,盡可能的去實現(xiàn)較“復(fù)雜”的操作。
其實,乍一看,這個項目不復(fù)雜:
爬蟲獲取數(shù)據(jù),并保存到數(shù)據(jù)庫。
后端從數(shù)據(jù)獲取數(shù)據(jù),發(fā)送到前端。
前端一個搜索框,根據(jù)輸入的姓氏,從后端得到姓名并展示。
我在這個項目中主要用到的是這些技術(shù):requests,flask,vue。有些知識我也不是很懂,都是直接一邊學(xué)一邊用,我覺得學(xué)以致用,更能感受到學(xué)習(xí)的樂趣。
數(shù)據(jù)獲取
網(wǎng)站分析
需要從姓名大全這個網(wǎng)站獲取的數(shù)據(jù)有:
百家姓列表:['趙','錢','孫','李',......]
每個姓對應(yīng)的男生名、女生名
獲取一個百家姓列表,主要是為了判斷前端輸入的姓氏是否正確。
一、在谷歌瀏覽器中,右擊“檢查”,通過一番分析后,找到一個a標(biāo)簽,如下形式,可以通過這個標(biāo)簽,獲取到該姓氏的具體的超鏈接(通過href獲?。约鞍偌倚樟斜恚ㄍㄟ^文本拆分獲?。?。
<a class="btn btn2" title="趙姓名字大全共有趙姓名字162998個">趙姓名字大全</a>
二、繼續(xù)分析,點擊超鏈接進(jìn)入到姓氏的詳情頁,發(fā)現(xiàn)每個姓氏都有男生和女生的超鏈接,如下:
男生網(wǎng)站為:http://zhao.resgain.net/name/boys.html
女生網(wǎng)站為:http://zhao.resgain.net/name/girls.html
與之前分析的a標(biāo)簽中的href中的超鏈接有所不同,所以需要將/name_list.html替換為/name/boys.html和/name/girls.html才能得到對應(yīng)姓氏的男生、女生姓名地址。
三、進(jìn)入男生姓名網(wǎng)站中,每個姓名的html代碼如下:
<a href="#" class="livemsg" data-name="趙竹林"><span class="glyphicon glyphicon-comment"></span></a>
通過class="livemsg"定位到該a標(biāo)簽,然后獲取到data-name的屬性值即可得到姓名。
數(shù)據(jù)庫設(shè)計
這里選擇sqlite3為數(shù)據(jù)庫,主要是因為不需要復(fù)雜的配置,操作簡單,輕便小巧。
這里只需要存姓名(NAME)、性別(SEX)兩個字段即可,當(dāng)然,再加一個自增的ID字段。
數(shù)據(jù)庫設(shè)計代碼如下:
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數(shù)據(jù)庫對象,只需傳入database參數(shù),這里傳的tools_app.db表示,連接的是同級目錄下的名為tools_app.db的數(shù)據(jù)庫(數(shù)據(jù)庫不需要提前創(chuàng)建)
2、cursor為操作數(shù)據(jù)庫的“游標(biāo)”,可以把數(shù)據(jù)庫比喻成一個倉庫,游標(biāo)就像是一個優(yōu)秀的員工,cursor.execute()就是這個員工需要對這個倉庫做的事情;conn.commit()就是員工操作完后,倉庫將操作的結(jié)果告訴員工。conn.close()倉庫關(guān)門,期待下一次的開啟。
這個函數(shù)運行完之后,會在同級目錄下創(chuàng)建一個名為tools_app.db的數(shù)據(jù)庫,該數(shù)據(jù)庫中有一個名為RANDON_NAME的表。
爬蟲設(shè)計
通過之前的網(wǎng)站分析,爬蟲的運行應(yīng)該分為以下三步:
通過姓名大全地址獲取到每個姓氏href和文本信息。
通過第一步獲取的href,獲取男生和女生的網(wǎng)站。
通過第二步獲取的網(wǎng)站獲取對應(yīng)的姓名。
第一步,代碼詳解:
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就可以獲取到對應(yīng)的網(wǎng)頁對象,使用res.text屬性獲取到網(wǎng)頁源碼
2、使用BeautifulSoup格式化網(wǎng)頁,可以很方便,很快速的定位到需要的數(shù)據(jù)。
3、soup.find_all(attrs={'class': 'btn btn2'}),通過class屬性為btn btn2找到所有符合條件的元素,返回的是一個列表
4、通過s.get('href')方法可以獲取到標(biāo)簽元素的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可以獲取該標(biāo)簽中的文本內(nèi)容
2、split("姓")方法,按照"姓"分隔字符串,結(jié)果為一個列表,選擇第一個即為百家姓的姓氏。例如:"趙姓名字大全"分隔后變成:["趙","姓名字大全"]
第二步,構(gòu)造男生和女生的詳情鏈接:
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(需要替換的內(nèi)容,替換的內(nèi)容)
第三步,通過構(gòu)造的鏈接獲取姓名:
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數(shù)據(jù)庫,創(chuàng)建一個cursor對象,用來操作數(shù)據(jù)庫
2、同樣,使用soup.find_all()獲取到所有的標(biāo)簽,通過get方法獲取到姓名數(shù)據(jù)
3、構(gòu)造sql語句,用format()方法格式化字符串
4、cursor.execute()執(zhí)行sql插入語句,conn.commit()提交記錄,最后for循環(huán)結(jié)束后,conn.close()關(guān)閉連接
開始運行:
if __name__ == '__main__':
# 創(chuàng)建數(shù)據(jù)庫和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)
爬蟲獲取數(shù)據(jù)總計耗時2小時左右,獲取到數(shù)據(jù)249298條。

前端設(shè)計
采用Bootstrap4前端框架、vue2.js框架、vue-ajax-axios異步加載框架。
頁面構(gòu)思
需要一個簡單的搜索框,還需要兩個列表組來展示搜索的男生和女生姓名。
第一次進(jìn)入,會有默認(rèn)的頁面,默認(rèn)加載鄭姓的數(shù)據(jù)。
在前端這里判斷輸入的姓氏,是否在百家姓里面,如果不在,將確定按鈕改為紅色“禁用”。
-
輸入正確的姓氏后,每次點擊確定按鈕,展示的姓名都是隨機(jī)的。
確定頁面展示:
前端確定頁面.png
禁用頁面展示:
前端錯誤頁面.png
代碼分析
卡片
將頁面卡片化,會感覺更加的清晰明了,只需要將輸入框放到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>
輸入框
這里有許多漂亮的輸入框,選擇一個簡單的即可。
<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>
當(dāng)然,直接拿來用是不行的,需要修改一番。使用vue框架的v-if來判斷輸入的姓氏是否正確,用v-on:click來觸發(fā)點擊事件。
1、判斷一個元素在不在一個數(shù)組中,js的判斷方法為:indexOf(),結(jié)果為-1表示不在數(shù)組中。
2、v-if和v-else所在的標(biāo)簽也緊挨著,才能生效。
修改代碼如下:
<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>
列表組
這里涉及到男生和女生,所以選擇將兩個列表組,并排放置在同一行中。
<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的語法
由于vue的模板是{{}},與flask中內(nèi)置的jinja2語法有沖突,所以需要用delimiters參數(shù)將vue的模板改為[{}]。
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)
})
}
}
})
后端
根據(jù)前端的分析,后端需要2個接口,index和get_names接口,index負(fù)責(zé)展示頁面,get_names返回查詢的姓名。
index接口
@app.route("/")
def index():
return render_template("random_name.html")
get_names接口
@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")
# 根據(jù)input_name從數(shù)據(jù)庫隨機(jī)獲取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”,回復(fù)“姓名”即可獲取源碼。

