Halo部落格的百度定時頁面提交

demoli發表於2022-11-24

Halo部落格的百度定時頁面提交

前言

  • 好不容易搭建好部落格,寫了些自以為有點意思的文章,但是沒人看!!因為沒有提交到搜尋引擎,所以根本沒人能搜到嘛~。雖然Next主題提供了百度自動提交的配置,但是百度收錄已經不再提供推動收錄的服務,所以Next主題的配置也沒啥用了
  • 百度收錄網站中提供了三種收錄方式,其中API提交最快捷,因此考慮使用Java實現將Halo部落格文章推送到百度收錄中

    • API提交
    • sitemap提交
    • 手動提交
  • Halo提供了用於獲取文章列表的API,因此思路很簡單:使用Java定時任務執行緒池按照固定的時間間隔從Halo API中獲取全部的文章連結,然後呼叫百度收錄API,向百度提交文章連結

    • 百度收錄對於頻繁提交舊的連結有一定的限制,如果經常重複提交舊連結,會下調配額,甚至可能會失去API推送功能的許可權,如果經常提交新的文章連結,可能適當提高配額。因此需要建立一個簡單的快取,提交連結時濾除舊的已經提交過的連結
  • 儘管Google使用站點地圖就已經能很好地進行連結的抓取了,不用單獨提交,但是Google同樣推薦使用indexing API主動提交要收錄的連結,具體可參考Halo部落格的谷歌定時頁面提交

工程搭建

  • 建立Gradle工程,配置檔案如下所示

    plugins {
        id 'java'
        id 'application'
    }
    
    group 'xyz.demoli'
    version '1.0-SNAPSHOT'
    
    
    sourceCompatibility = 1.11
    
    mainClassName="xyz.demoli.Main"
    
    repositories {
        mavenCentral()
    }
    
    application{
        applicationDefaultJvmArgs = ['-Duser.timezone=GMT+8']
    }
    
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
        // https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
        implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.9.3'
        implementation 'com.google.code.gson:gson:2.9.0'
        // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api
        implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
        // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
        implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
        // https://mvnrepository.com/artifact/org.projectlombok/lombok
        compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.22'
        annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.22'
    }
    
    test {
        useJUnitPlatform()
    }
    • annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.22'保證gradle專案中lombok的註解可以被正確解析
    • applicationDefaultJvmArgs引數的設定是為了解決後續服務部署在容器中時日誌列印時間不是東八區時區的問題
  • 配置檔案如下:

    prefix=https://blog.demoli.xyz
    postAPI=%s/api/content/posts?api_access_key=%s&page=%d
    apiAccessKey=***
    baiduUrl=http://data.zz.baidu.com/urls?
    token=***
    • apiAccessKey是在Halo部落格設定中設定的

      image-20220327184136467

    • prefix是Halo部落格的首頁訪問URL
    • token是百度提交平臺為使用者提供的提交token,在百度提交網站頁面中有展示
  • 日誌配置檔案如下:

    <?xml version="1.0" encoding="utf-8" ?>
    
    <configuration status="INFO">
        <appenders>
            <console name="console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            </console>
        </appenders>
    
        <loggers>
            <root level="INFO">
                <appender-ref ref="console"/>
            </root>
        </loggers>
    </configuration>
  • 整個工程只有兩個核心類

    • PostScrap

      import com.google.gson.Gson;
      import com.google.gson.JsonArray;
      import com.google.gson.JsonElement;
      import com.google.gson.JsonObject;
      import java.io.IOException;
      import java.io.InputStream;
      import java.util.ArrayList;
      import java.util.HashSet;
      import java.util.List;
      import java.util.Properties;
      import java.util.Set;
      import java.util.stream.Collectors;
      import okhttp3.OkHttpClient;
      import okhttp3.Request;
      import okhttp3.Response;
      
      /**
       * 使用Halo API獲取文章連結
       */
      public class PostScrap {
      
          static private String postAPI;
          static private String apiAccessKey;
          static private String prefix;
          // 快取
          static private final Set<String> links = new HashSet<>();
      
          // 注意properties配置檔案中字串不用加引號
          static {
              try (InputStream stream = PostScrap.class.getResourceAsStream("/config.properties")) {
                  Properties properties = new Properties();
                  properties.load(stream);
                  apiAccessKey = properties.getProperty("apiAccessKey");
                  prefix = properties.getProperty("prefix");
                  postAPI = properties.getProperty("postAPI");
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 發起請求獲取全部文章連結
           *
           * @return
           */
          public static List<String> getPosts() {
      
              List<String> res = new ArrayList<>();
      
              OkHttpClient client = new OkHttpClient();
              Request initialRequest =
                  new Request.Builder().get().url(String.format(postAPI, prefix, apiAccessKey, 0))
                      .build();
      
              try (Response response = client.newCall(initialRequest).execute()) {
                  res = handlePage(response, client);
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return res;
          }
      
          /**
           * 處理分頁
           *
           * @param initialResponse
           * @param client
           * @return
           * @throws IOException
           */
          private static List<String> handlePage(Response initialResponse, OkHttpClient client)
              throws IOException {
      
              JsonObject jsonObject =
                  new Gson().fromJson(initialResponse.body().string(), JsonObject.class);
              JsonArray array = jsonObject.get("data").getAsJsonObject().get("content").getAsJsonArray();
              int pages = jsonObject.get("data").getAsJsonObject().get("pages").getAsInt();
      
              // jsonArray轉為List
              List<String> posts = new ArrayList<>();
              for (JsonElement element : array) {
                  posts.add(element.getAsJsonObject().get("fullPath").getAsString());
              }
      
              // 分頁查詢
              for (int i = 1; i < pages; i++) {
                  Request request =
                      new Request.Builder().get().url(String.format(postAPI, prefix, apiAccessKey, i))
                          .build();
                  try (Response response = client.newCall(request).execute()) {
                      jsonObject = new Gson().fromJson(response.body().string(), JsonObject.class);
                      array = jsonObject.get("data").getAsJsonObject().get("content").getAsJsonArray();
                      for (JsonElement element : array) {
                          posts.add(element.getAsJsonObject().get("fullPath").getAsString());
                      }
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
      
              // 快取過濾
              return posts.stream().map(content -> prefix + content).filter(links::add)
                  .collect(Collectors.toList());
          }
      }
    • BaiduSubmitter

      import com.google.gson.Gson;
      import com.google.gson.JsonObject;
      import java.io.IOException;
      import java.io.InputStream;
      import java.util.Optional;
      import java.util.Properties;
      import lombok.extern.log4j.Log4j2;
      import okhttp3.MediaType;
      import okhttp3.OkHttpClient;
      import okhttp3.Request;
      import okhttp3.RequestBody;
      import okhttp3.Response;
      
      /**
       * 提交百度收錄
       */
      @Log4j2
      public class BaiduSubmitter {
      
          static private String submitUrl;
      
          static {
              try (InputStream stream = PostScrap.class.getResourceAsStream("/config.properties")) {
                  Properties properties = new Properties();
                  properties.load(stream);
                  String baiduUrl = properties.getProperty("baiduUrl");
                  String site = properties.getProperty("prefix");
                  String token = properties.getProperty("token");
                  submitUrl = baiduUrl + "site=" + site + "&token=" + token;
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      
          /**
           * 提交連結
           */
          public static void submit() {
      
              OkHttpClient client = new OkHttpClient();
              Optional<String> urlStrings =
                  PostScrap.getPosts().stream().reduce((out, url) -> out + "\n" + url);
              if (urlStrings.isEmpty()) {
                  log.info("無新增文章");
                  return;
              }
              RequestBody body = RequestBody.create(MediaType.get("text/plain"), urlStrings.get());
              Request request = new Request.Builder().post(body).url(submitUrl)
                  .header("Content-Type", "text/plain")
                  .build();
              try (Response response = client.newCall(request).execute()) {
                  JsonObject jsonObject = new Gson().fromJson(response.body().string(), JsonObject.class);
                  if (jsonObject.get("error") != null) {
                      log.error("提交失敗: {}", jsonObject.get("message").getAsString());
                  }
                  log.info("提交成功 {} 條連結,剩餘額度: {},連結清單如下:", jsonObject.get("success").getAsInt(),
                      jsonObject.get("remain").getAsInt());
                  log.info(urlStrings.get());
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
  • Main

    public class Main {
    
        public static void main(String[] args) {
            Executors.newScheduledThreadPool(1)
                .scheduleWithFixedDelay(BaiduSubmitter::submit, 0, 12, TimeUnit.HOURS);
        }
    }

工程部署

  • 專案根目錄執行gradle build -x test
  • build/distributions/BaiduSubmitter-1.0-SNAPSHOT.tar複製到安裝有Java環境的伺服器

    tar xf BaiduSubmitter-1.0-SNAPSHOT.tar`
    cd BaiduSubmitter-1.0-SNAPSHOT
    nohup bin/BaiduSubmitter > nohup.out &
  • tail -f nohup.out檢視日誌

補充

  • 博主是一個Docker容器的究極愛好者,因為使用容器可以保證宿主機環境的”純淨“,所以這裡補充使用Docker容器部署服務的方式
  • 首先將專案構建得到的軟體包build/distributions/BaiduSubmitter-1.0-SNAPSHOT.tar複製到伺服器,解壓並重新命名,建立Dockerfile

    tar xf BaiduSubmitter-1.0-SNAPSHOT.tar
    mkdir -p blogSubmitter/baiduSubmitter
    mv BaiduSubmitter-1.0-SNAPSHOT blogSubmitter/baiduSubmitter/baidu
    cd blogSubmitter/baiduSubmitter
    touch Dockerfile
  • Dockerfile檔案如下:

    FROM openjdk:11
    COPY . /submitter
    WORKDIR /submitter
    # 更改時區
    RUN rm -rf /etc/localtime
    RUN ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    CMD ["nohup","baidu/bin/BaiduSubmitter"," &"]
  • 建立yaml配置檔案,使用Docker Compose構建服務

    cd blogSubmitter
    touch submitter.yaml
    version: '3.1'
    services:
      blog-baidu-submitter: 
        build: ./baiduSubmitter 
        container_name: blogBaiduSubmitter
        restart: unless-stopped
  • 執行docker-compose -f submitter.yaml up -d建立服務

注意事項

  • 如果更改了原始碼,需要重新構建映象,此時要把之前的映象刪除(應該有更好的解決辦法,有待改善,比如使用volume的方式執行掛載)

參考

相關文章