昨天突然发现了dht爬虫可以用bitmagnet自部署,欣喜若狂啊,以前有什么手撕包菜什么的,不过早就不行了。
为什么自部署?因为数据库在自己这里啊,想怎么查询怎么查询,能精准的找到自己的需要。
连夜部署好bitmagnet,然后再手搓好api和页面,不错不错。。。 因为过于那个,demo不敢放了,只放一下代码吧。
api的功能是在数据库中随机查找包含中文(不允许日文平片假名),然后包含的压缩文件大小比例不超过50%的。
后面两个要求我不解释,我只解释一下随机。。知识爆炸文件爆炸,爆炸到连关键词都想不出来了,所以,随机随机,也许有喜欢的。 🙂🙂
package main
import (
"database/sql"
"log"
"net/http"
"strings"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
type Torrent struct {
InfoHash string `json:"info_hash"`
SizeGB float64 `json:"size_gb"`
Name string `json:"name"`
}
func main() {
r := gin.Default()
r.GET("/api/rantorrents", handleGetTorrents)
if err := r.Run(":8000"); err != nil {
log.Fatal(err)
}
}
func handleGetTorrents(c *gin.Context) {
db, err := sql.Open("postgres", "host=127.0.0.1 port=5432 dbname=bitmagnet user=postgres password=postgres sslmode=disable")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db error"})
return
}
defer db.Close()
const query = `
SELECT encode(info_hash, 'hex') AS info_hash, size, name
FROM torrents
WHERE name ~ '[\u4e00-\u9fff]'
AND name !~ '[\u3040-\u309f]'
AND name !~ '[\u30a0-\u30ff]'
ORDER BY RANDOM()
LIMIT 100;
`
rows, err := db.Query(query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "query fail"})
return
}
defer rows.Close()
var results []Torrent
compressedExts := []string{".zip", ".rar", ".7z", ".tar", ".gz", ".bz2"}
for rows.Next() {
var infoHash, name string
var size int64
if err := rows.Scan(&infoHash, &size, &name); err != nil {
continue
}
var totalFileSize int64
var compressedSize int64
filesQuery := `
SELECT path, size
FROM torrent_files
WHERE info_hash = decode($1, 'hex')
`
fileRows, err := db.Query(filesQuery, infoHash)
if err != nil {
continue
}
for fileRows.Next() {
var path string
var fileSize int64
if err := fileRows.Scan(&path, &fileSize); err != nil {
continue
}
totalFileSize += fileSize
ext := strings.ToLower(path)
for _, suffix := range compressedExts {
if strings.HasSuffix(ext, suffix) {
compressedSize += fileSize
break
}
}
}
fileRows.Close()
if totalFileSize == 0 || float64(compressedSize)/float64(totalFileSize) > 0.5 {
continue }
t := Torrent{
InfoHash: infoHash,
SizeGB: float64(size) / (1024 * 1024 * 1024),
Name: name,
}
results = append(results, t)
if len(results) >= 20 {
break
}
}
c.JSON(http.StatusOK, results)
}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>随机磁链</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
margin: 0;
font-family: sans-serif;
background: #f5f5f5;
display: flex;
flex-direction: column;
align-items: center;
}
.button {
margin: 10px;
padding: 8px 16px;
background: #3498db;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
}
.list {
width: 100%;
max-width: 800px;
padding: 10px;
box-sizing: border-box;
}
.torrent {
background: #fff;
margin: 6px 0;
padding: 10px;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
.name {
font-size: 15px;
cursor: pointer;
word-break: break-word;
}
.copied {
font-size: 12px;
color: green;
margin-left: 10px;
display: none;
}
@media (max-width: 600px) {
.name {
font-size: 14px;
}
}
</style>
</head>
<body>
<button class="button" onclick="loadTorrents(false)">🔄 刷新</button>
<div class="list" id="list"></div>
<script>
let loading = false;
async function loadTorrents(append = true) {
if (loading) return;
loading = true;
try {
const res = await fetch('/api/rantorrents');
const data = await res.json();
console.log('收到的数据:', data); // 调试:输出数据检查
const list = document.getElementById("list");
if (!append) list.innerHTML = "";
if (data.length === 0) {
console.log('没有数据!');
return;
}
data.forEach(item => {
const div = document.createElement("div");
div.className = "torrent";
const name = document.createElement("div");
name.className = "name";
// 修复size字段,确保是有效的数字,并且以GB为单位显示
const sizeInGB = item.size_gb ? item.size_gb.toFixed(2) : "0.00";
name.textContent = `[${sizeInGB} GB] ${item.name}`;
const copied = document.createElement("span");
copied.className = "copied";
copied.textContent = "✔ 已复制";
const magnet = `magnet:?xt=urn:btih:${item.info_hash}`;
name.onclick = async () => {
try {
await navigator.clipboard.writeText(magnet);
copied.style.display = "inline";
setTimeout(() => (copied.style.display = "none"), 1500);
} catch (err) {
alert("复制失败:" + err);
}
};
div.appendChild(name);
div.appendChild(copied);
list.appendChild(div);
});
// 限制最多保留 100 条
while (list.children.length > 100) {
list.removeChild(list.firstChild);
}
} catch (err) {
console.error("加载失败:", err);
}
loading = false;
}
// 初始加载
loadTorrents();
// 无限滚动:接近底部时加载更多
window.addEventListener("scroll", () => {
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 200)) {
loadTorrents(true);
}
});
</script>
</body>
</html>
