分类 工作&技术 下的文章

昨天突然发现了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>

bt.jpg

今天在玩腾讯的vscode的ai编程插件。
看来腾讯的人都是用Mac的,不过就算不用windows,没见过马屎,但也该见过马跑呀!Windows里哪来的cmd键你倒是说说呀? 而且,一般别人的cmd是用windows徽标键,但他不是,按照软文中ctrl-windows-i也不对。
第一张图是vscode里的codebuddy的提示。
第二张图是腾讯发的软文。
问题不大,不过真的很可笑。
上面只是笑话他们不认真测试,但有句说句,相比copilot的1000次限制,cursor的20刀一个月,腾讯这是免费全功能服务,也不用要求太高了,还是赞一下。

cmd1.jpg
cmd2.jpg

看来百业萧条,ovh居然也出盲盒了,基准配置是 22.99o/月,E5-1650v3 64g 2*480ssd 1g。
本着愚人精神,我赌把手气,还不错,v3 -> v4, 2*480 ssd -> 2*1.2t nvme, 64g -> 128g。
卖了溢价几十百把块也没意思,还不如自己拿来乐乐吧,这么高性价比的机器真的难找。
1ip的博客现在挂在上面了,也不套cf了,直连吧。
~ 让我再好好想想怎么创造一点需求出来。
(经不起高溢价的诱惑,今天已经把它出了。。。)

前因后果不扯了,我前几篇贴文都在说这个事,简单说就是上周系统大并发下炸了。
今天的流量比上周还大一倍,但数据库和服务器完全是在低负荷下运行。(数据库有的那个尖峰,是因为数据库缩容后比例尺变化显示原因,不是真的大负荷)
开心。

success.png
success1.png