自己在空閒的時間,用 Django 開發的一個短網址系統,可以支援自訂 FB 網址縮圖的功能。獨立的 app 可以很方便地整合進已存在的 Django 專案裡面。
以下是程式碼:source code
需要討論的問題
開發這個系統,實作上沒有很困難,但網路上似乎沒有比較完整的方法,所以就記錄一下開發的細節。有幾個需要思考的點:
- 整體架構
- 社群網站如何產生預覽?
- 「短」網址如何產生?
- 使用哪一種方法 redirect? (javascript or python / 301 or 302)
- 如何記錄流量
第一版的功能
目前釋出的第一個版本,有幾個功能:
快速縮網址
用起來有點像 goo.gl
自訂短網址
可以自訂短網址的名稱,如:xxx.com/links/活動報名
,還能自訂 FB 預覽縮圖、標題、內容,對於臉書粉絲團來說非常實用的功能。像這樣子:
查看數據
每次點擊都把他記錄起來,有點像是 google analytics 的作用,就自己刻了一個,像這樣子:
整體架構
短網址主要的思路很簡單,
- 使用者從 xxx.com/meow 進入伺服器
- 到資料庫裡找 meow 所對應的網址
- 做一些統計之類的事
- 將他導向目標
# models.py
class ShortURL(models.Model):
timestamp = models.DateTimeField(default=timezone.now)
target = models.CharField(max_length=200)
name = models.CharField(max_length=200, blank=True)
title = models.CharField(max_length=200, blank=True)
description = models.TextField(blank=True)
thumbnail = models.ImageField(upload_to='thumbnails/%m/', blank=True)
permanent_url = models.CharField(max_length=20, blank=True)
mode = models.IntegerField(default=301)
社群網站如何產生預覽?
基當你貼了一個網址之後,網站會讓一隻爬蟲程式去看看你的網站,FB 會用到 Title、Description、image 等內容,如果你有特別設定 meta tag
他會優先使用 meta tag 的內容,若沒有的話才是亂亂抓(通常是第一段文字及第一張照片)。
所以為了要「騙」過這支爬蟲程式,我就 render 一個假的、帶有自訂 meta 的網頁,然後再用 javascript redirect 至目標網站。在使用上因為馬上就被重導,所以使用者不會感覺到發生了什麼事。
# views.py
# ...
context = {
'url': shortcut.target,
'title': shortcut.title,
'description': shortcut.description,
'image': image_url
}
return render(request, 'links/redirect.html', context)
<meta property="og:title" content="{{title}}" />
<meta property="og:description" content="{{description}}" />
<meta property="og:image" content="{{image}}" />
<script type="text/javascript">
window.location.href = "{{url}}";
</script>
永久短網址如何產生?
說到永久短網址,就要產生一段夠短的字串來對應原本的網址,要兼具「短」且要「唯一」,且必須具有一定的亂度,避免不公開的網址被連結到(如內部的表單)。
所以我使用的方法是「用objects.create() 的 id + hash_salt 經過 hash 後取前 6 位,再換成 BASE64
」。加入 Hash salt 讓網址規則更難被找到。使用 BASE 64 讓網址可以為 A-Z,a-z,0-9 加上 -_ ,如:xxx.com/links/A-w0
變得更有短網址的樣子了呢。
permanent_url = base64.b64encode(
hashlib.md5((str(shortcut.id) + HASH_SALT).encode('utf-8')).digest(), altchars=b"-_")[:6].decode("utf-8")
但這樣的方法有一個缺點,就是 md5[:6]
並不一定唯一,但經過實驗後大約要生成 100000 個網址之後才會發生碰撞,所以就先安心地用吧~若真的遇到了也可以用加字的方式解決。
或許可以試試看
將 id HASH 成 32 位元,2^32=4294967296 可以用 6 個字 64^6=68719476736 包起來。
使用哪種方法 redirect?
- 301 redirect 301 Move Permanently。可以簡單地理解為該資源已經被永久改變了位置。
- 302 redirect 302 Found,原始描述短語為 Moved Temporarily。可以簡單的理解為該資源原本確實存在,但已經被臨時改變了位置;換而言之,就是請求的資源暫時駐留在不同的 URI 下,故而除非特別指定了快取頭部指示,該狀態碼不可快取。
快速轉址
在實作快速轉址時,我是使用 301 重導的概念去做,因為使用者只是快速的經過這個網站,所以我在後端
就直接 redirect(‘target.com’) ,也暗示 FB 直接去找原本的網站找東西。
自訂轉址
自訂轉址由於是要讓爬蟲程式造訪假網站,再由 前端javascript 重導至新網站,算是以 302 的概念去實作。
# views.py
if shortcut.mode == 301:
return redirect(shortcut.target)
elif shortcut.mode == 302:
# ...
context = {
'url': shortcut.target,
'title': shortcut.title,
'description': shortcut.description,
'image': image_url
}
return render(request, 'links/redirect.html', context)
如何記錄流量?
每點擊一次,進到 views.py 時就 create 一個 object, ForeignKey 至連結的網址,到時候要製作統計圖時就能很方便的撈資料了~順便記錄了訪問 ip 也可以統計不重複的 ip 瀏覽量。
然後再用 Chart.js 畫圖。
# models.py
class Viewer(models.Model):
timestamp = models.DateTimeField(default=timezone.now)
short_url = models.ForeignKey(
ShortURL, on_delete=models.CASCADE, related_name='viewers')
ip = models.CharField(max_length=100)