Python爬蟲+Flask,帶你創(chuàng)建車標(biāo)學(xué)習(xí)網(wǎng)站

文化不分邊界
人,為什么要讀書?舉個例子:
當(dāng)看到天邊飛鳥,你會說:“落霞與孤鶩齊飛,秋水共長天一色。”而不是:“臥靠,好多鳥。”;
當(dāng)你失戀時你低吟淺唱道:“人生若只如初見,何事秋風(fēng)悲畫扇。”而不是千萬遍地悲喊:“藍(lán)瘦,香菇!”
別人看車關(guān)注牌子,我看車關(guān)注寬敞不,睡著舒服不?可不管怎樣不能在人前丟份啊,所以我決定學(xué)習(xí)學(xué)習(xí)車標(biāo)!首先我們爬取車標(biāo)及其相關(guān)信息,然后通過Flask來做一個車標(biāo)學(xué)習(xí)網(wǎng)站。
先來看看實現(xiàn)效果:

車標(biāo)網(wǎng)數(shù)據(jù)爬蟲
在網(wǎng)上找了半天車標(biāo)的數(shù)據(jù),最后看到了這個網(wǎng)站:
車標(biāo)網(wǎng)? http://www.chebiaow.com/logo。

網(wǎng)站將車系按照字母從A-Z進(jìn)行了排序,然后點(diǎn)擊每個車標(biāo)進(jìn)入詳細(xì)信息,那Audi做例子:

有用的數(shù)據(jù)是哪些?品牌名稱、車標(biāo)圖片、成立時間、主要車型、官網(wǎng)。
那么讓我們開始通過爬蟲,獲取車標(biāo)網(wǎng)下所有的汽車品牌及車標(biāo),最終入庫保存吧,開始!
數(shù)據(jù)庫操作指南
針對簡單的數(shù)據(jù),我習(xí)慣用python自帶的sqlite3進(jìn)行數(shù)據(jù)庫的存儲,簡單方便….那么如何管理我們的數(shù)據(jù)庫呢?推薦使用DBUtils!
安裝:pip install DBUtils
DBUtils is a suite of tools providing solid, persistent and pooled connections to a database that can be used in all kinds of multi-threaded environments like Webware for Python or other web application servers. The suite supports DB-API 2 compliant database interfaces and the classic PyGreSQL interface.
簡而言之,DBUtils是一套為數(shù)據(jù)庫提供可靠,持久和池式連接的工具,可用于各種多線程環(huán)境。我們一般使用DBUtils.PooledDB來創(chuàng)建一批連接池進(jìn)行并發(fā)處理。常用參數(shù)如下:
| 參數(shù) | 說明 |
|---|---|
| creator | 使用鏈接數(shù)據(jù)庫的模塊(sqllite3、pymysql…) |
| maxconnections | 連接池允許的最大連接數(shù),0和None表示不限制連接數(shù) |
| mincached | 初始化時,鏈接池中至少創(chuàng)建的空閑的鏈接,0表示不創(chuàng)建 |
| maxcached | 鏈接池中最多閑置的鏈接,0和None不限制 |
| blocking | 連接池中如果沒有可用連接后,是否阻塞等待。True,等待;False,不等待然后報錯 |
| maxusage | 一個鏈接最多被重復(fù)使用的次數(shù),None表示無限制 |
| host | ip |
| user | 用戶名 |
| password | 密碼 |
| database | 數(shù)據(jù)庫名 |
| charset | 字符集(Mysql用的比較多,SQLite沒有) |
因為之前都是拿DBUtils鏈接Mysql數(shù)據(jù)庫的,這次默認(rèn)就直接改成sqlite3,結(jié)果簡單配置下,封裝上常用的方法…一跑程序掛了!Why?
SQLite本身無法應(yīng)對多個線程并發(fā)訪問,由一個線程創(chuàng)建并訪問的sqlite的數(shù)據(jù)庫,無法允許另外一個線程進(jìn)行訪問,找解決辦法唄,最終找到通過設(shè)置check_same_thread=False,使SQLite支持多線程并發(fā)(但并發(fā)的效果很一般)。
#?-*-?coding:?utf-8?-*-
#?@Author???:?王翔
#?@微信號???:?King_Uranus
#?@公眾號????:?清風(fēng)Python
#?@GitHub???:?https://github.com/BreezePython
#?@Date?????:?2019/12/15?20:27
#?@Software?:?PyCharm
#?@version ?:Python 3.7.3
#?@File?????:?db_maker.py
import?sqlite3
from?DBUtils.PooledDB?import?PooledDB
class?DB_Maker:
????def?__init__(self):
????????self.POOL?=?PooledDB(
????????????check_same_thread=False,
????????????creator=sqlite3,??#?使用鏈接數(shù)據(jù)庫的模塊
????????????maxconnections=10,??
????????????mincached=2,??
????????????maxcached=5,??
????????????blocking=True,??
????????????maxusage=None,??
????????????ping=0,
????????????database='database.db',
????????)
????????self.check_db()
????def?check_db(self):
????????sql?=?"SELECT?name?FROM?sqlite_master?where?name=?"
????????if?not?self.fetch_one(sql,?('idiom',)):
????????????self.create_table()
????def?create_table(self):
????????print("create?table?...")
????????sql?=?"""create?table?idiom?(
????????????????????????[id]????????????integer?PRIMARY?KEY?autoincrement,
????????????????????????[name]?????????varchar?(10),
????????????????????????[speak]??????varchar?(30),
????????????????????????[meaning]??????varchar?(100),
????????????????????????[source]??????varchar?(100),
????????????????????????[example]??????varchar?(100),
????????????????????????[hot]??????int(10)
????????????????????)"""
????????self.fetch_one(sql)
????def?db_conn(self):
????????conn?=?self.POOL.connection()
????????cursor?=?conn.cursor()
????????return?conn,?cursor
????@staticmethod
????def?db_close(conn,?cursor):
????????cursor.close()
????????conn.close()
????def?fetch_one(self,?sql,?args=None):
????????conn,?cursor?=?self.db_conn
????????if?not?args:
????????????cursor.execute(sql)
????????else:
????????????cursor.execute(sql,?args)
????????record?=?cursor.fetchone()
????????self.db_close(conn,?cursor)
????????return?record
????def?fetch_all(self,?sql,?args):
????????conn,?cursor?=?self.db_conn
????????cursor.execute(sql,?args)
????????record_list?=?cursor.fetchall()
????????self.db_close(conn,?cursor)
????????return?record_list
????def?insert(self,?sql,?args):
????????conn,?cursor?=?self.db_conn
????????row?=?cursor.execute(sql,?args)
????????conn.commit()
????????self.db_close(conn,?cursor)
本次有一個知識點(diǎn),我們需要將車標(biāo)圖片,存儲在數(shù)據(jù)庫中,那么如何在數(shù)據(jù)庫中存儲圖片,使用類型BLOB。舉一個簡單的數(shù)據(jù)庫圖片讀寫例子
#?-*-?coding:?utf-8?-*-
#?@Author???:?王翔
#?@微信號???:?King_Uranus
#?@公眾號????:?清風(fēng)Python
#?@GitHub???:?https://github.com/BreezePython
#?@Date?????:?2019/12/15?20:27
#?@Software?:?PyCharm
#?@version ?:Python 3.7.3
#?@File?????:?show.py
import?sqlite3
db?=?sqlite3.connect('Car.db')
cur?=?db.cursor()
cur.execute("CREATE?TABLE?if?not?exists?image_save?(image?BLOB);")
with?open('Audi.jpg',?'rb')?as?f:
????cur.execute("insert?into?image_save?values(?)",?(sqlite3.Binary(f.read()),))
????db.commit()
cur.execute('select?image?from?image_save?limit?1')
b?=?cur.fetchone()[0]
with?open('1.jpg',?'wb')?as?f:
????f.write(b)
我們創(chuàng)建一個image_save的測試表,然后將圖片讀取為二進(jìn)制字節(jié)的方式,通過sqlite3.Binary將二進(jìn)制文件存儲至數(shù)據(jù)庫。
那么同樣的,我們將BLOB類型的圖片讀取出來后,進(jìn)行寫入,即可達(dá)到效果,來看看這個1.jpg是否正常:

圖片下載小技巧
看過了二進(jìn)制的存儲方式,大家肯定說明白了,網(wǎng)站獲取到圖片鏈接然后找著上面的例子下載到本地,然后再進(jìn)行二進(jìn)制的讀取后存儲數(shù)據(jù)庫即可,對嗎?不對…有什么問題呢?來看一個例子:

這里Audi圖片的鏈接地址,我們通過requests來下載看看….
import?requests
r?=requests.get('http://img.chebiaow.com/thumb/cb/allimg/1303/1-1303061Z600520,c_fill,h_138,w_160.jpg')
r.content
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01...'
可以看到我們通過requests.get獲取到的content就已經(jīng)是二進(jìn)制數(shù)據(jù)了,為何還要存儲成圖片,在轉(zhuǎn)化呢?省去了我們保存圖片的多余過程。
網(wǎng)頁分析
針對A-Z的車標(biāo)排序,網(wǎng)站的url匹配關(guān)系很簡單:
from?string?import?ascii_uppercase?as?au
#?ascii_uppercase代表A-Z,當(dāng)然你可以不引入模塊自己生成也OK...
for?uppercase?in?au:
????"http://www.chebiaow.com/logo/{}.html".format(au)

可以看到在包含cb-list方法的ul下匹配所有l(wèi)i中的第一個a標(biāo)簽,然后拼接base_url即可。
進(jìn)入品牌詳情界面后,我們針對左右欄目的設(shè)置,分別獲取所需標(biāo)紅的內(nèi)容

最終存儲的數(shù)據(jù)庫如下:

由于圖片是BLOB類型的二進(jìn)制文件,所以大家看到的是星星,最終獲取網(wǎng)站258份車輛信息(雖然我能認(rèn)識的不到20種…)
這個中興看了半天還以為是搞錯了,沒想到是同名的…
萬法同源
一直覺得可能自己不太適合搞技術(shù),更適合在天橋底下支個攤子說書。技術(shù)的東西從來沒人關(guān)注,扯東扯西的文章莫名的火。之前的一篇文章MarkDown添加圖片的三種方式不管是在技術(shù)為主的CSDN還是娛樂為主的簡書,都莫名的火爆,看圖:

其實文章沒什么含量,就是介紹了下markdown添加圖片的方式,唯一新奇的可能就是使用了base64的圖片二進(jìn)制轉(zhuǎn)化。
![avatar]\(data:image/png;base64,iVBORw0......)使用python將圖片轉(zhuǎn)化為base64字符串
import?base64
f=open('723.png','rb')?#二進(jìn)制方式打開圖文件
ls_f=base64.b64encode(f.read())?#讀取文件內(nèi)容,轉(zhuǎn)換為base64編碼
f.close()
print(ls_f)
base64字符串轉(zhuǎn)化為圖片
import?base64
bs='iVBORw0KGgoAAAANSUhEUg....'?#?太長了省略
imgdata=base64.b64decode(bs)
file=open('2.jpg','wb')
file.write(imgdata)
file.close()
通過request.get(url).content獲取的二進(jìn)制字符串,直接存儲至SQLite數(shù)據(jù)庫的BLOB字段中。如果我們需要顯示圖片,直接通過open函數(shù)的寫入數(shù)據(jù)即可生成原始的圖片。但是,如果我不想寫入圖片,而希望直接展示在web界面上呢?也可以通過markdown添加圖片的方式,使用base64的編碼來實現(xiàn)!
Flask展示圖片例子
我們先不通過讀取數(shù)據(jù)庫,而是直接獲取requests.get(url).content的方式測試Flask的圖片展示。
HTML代碼:
<html?lang="en">
<head>
????<meta?charset="UTF-8">
????<title>Titletitle>
head>
<body>
<img?src="data:;base64,{{?img?}}">
body>
html>
Flask后臺代碼:
from?flask?import?Flask,?render_template
import?base64
import?requests
app?=?Flask(__name__)
@app.route("/show")
def?show_image():
????r?=?requests.get('http://img.chebiaow.com/thumb/cb/allimg/1303/1-1303061Z600520,c_fill,h_138,w_160.jpg')
????image?=?base64.b64encode(r.content).decode('ascii')
????return?render_template('index.html',?img=image)
if?__name__?==?'__main__':
????app.run()

圖片展示OK,使用這種方式,我們就沒必要將圖片文件先從數(shù)據(jù)庫中讀取生成后,再通過url_for('static',filename='x.png')的方式進(jìn)行顯示了。
完善車標(biāo)app
我們就把這些數(shù)據(jù)庫信息配合Flask完成一個簡單的車標(biāo)學(xué)習(xí)簡單網(wǎng)站吧,來看看實現(xiàn)效果:

后臺Flask代碼:
#?-*-?coding:?utf-8?-*-
#?@Author???:?王翔
#?@JianShu??:?清風(fēng)Python
#?@Date?????:?2019/7/25?1:37
#?@Software?:?PyCharm
#?@version ?:Python 3.7.3
#?@File?????:?app.py
from?flask?import?Flask,?render_template,?g
import?sqlite3
import?random
import?base64
app?=?Flask(__name__)
DATABASE?=?'static/db/car.db'
app.secret_key?=?'Breeze?Python'
def?connect_db():
????return?sqlite3.connect(DATABASE)
@app.before_request
def?before_request():
????g.db?=?connect_db()
@app.teardown_request
def?teardown_request(exception):
????if?hasattr(g,?'db'):
????????g.db.close()
def?query_db(query,?args=()):
????cur?=?g.db.execute(query,?args)
????rv?=?[dict((cur.description[idx][0],?value)
???????????????for?idx,?value?in?enumerate(row))?for?row?in?cur.fetchall()]
????if?not?query.startswith('select'):
????????g.db.commit()
????return?rv[0]?if?rv?else?None
@app.route('/car')
@app.route('/')
def?index():
????id?=?random.randint(1,?141)
????car_info?=?query_db('select?name,image,founded,models,website?from?car_logo?where?id={}'.format(id))
????car_info['image']?=?base64.b64encode(car_info['image']).decode('ascii')
????print(car_info)
????return?render_template('index.html',?car=car_info)
if?__name__?==?'__main__':
????app.run(host='0.0.0.0',?port=7000)
前臺HTML代碼就不再這里展示了…
公眾號回復(fù)車標(biāo),下載整套爬蟲與Flask代碼及數(shù)據(jù)庫信息。
推薦閱讀
THANKS
- End -
點(diǎn)個“在看”必升職加薪喔!
