🆕 Bricks 2.4 已发布:查询循环性能提升 40%,迁移教程同步更新 →

首页 / 中文教程 / 教程

单次 AJAX 调用从 API 导入自定义文章:特色图 + 自定义字段

教程:单次 AJAX 调用从 API 导入自定义文章:特色图 + 自定义字段——Bricks 附完整代码可直接复用,适合外贸独立站与 WordPress 开发者。

Ray ChanRay Chan·2026-08-12·约 6 分钟
目录
  1. 1.目录
  2. 2.引言
  3. 3.获取 API 访问权限
  4. 4.获取 API 响应
  5. 5.创建自定义文章
  6. 6.设置特色图
  7. 7.填充 ACF 字段
  8. 8.启用 AJAX
  9. 9.完整代码
  10. 10.运行 AJAX 调用

本教程将演示如何通过抓取一个公开 API,把一批电影(含图片和自定义字段)导入到自定义文章类型中。

目录

引言

首先说明,本教程是 Cédric Bontems 最近在群里分享的那个视频的后续——他在视频里详细讲解了如何从 API 抓取数据重建 Netflix 首页。本教程采用不同的思路达到同样目的:把数据导入 WordPress,而不是每次页面加载时都调用 API。

为什么要这么做?好处不少:

  • 用这个方法,你只需要抓取一次 API。数据导入完成后甚至可以完全删掉这段代码,因为所有内容都已存在 WordPress 数据表里。

  • 数据存进 WordPress 后,你可以不用写任何自定义 PHP 代码就能操作它——在 Bricks 构建器里创建查询循环、嵌套滑块、筛选器、单篇页面、分类页面等等。

  • 用 WordPress 表里的数据比每次调 API 快得多,内容和 WP 查询都可以缓存。

  • 内容展示不依赖外部源:就算 API 某天挂了,你也不受影响。

本教程还大量参考了 WPCasts 的视频,建议去他的频道学习更多高级 WordPress 技巧。

获取 API 访问权限

本教程使用 Cédric 分享的同一个 API:https://www.themoviedb.org/settings/api。他在视频里讲过怎么注册账号、拿到访问权限,这里就不赘述了。记得把 API key 定义在 functions.php 里:

define ('TMDB_API_KEY', '<YOUR_API_KEY>' );

获取 API 响应

我们沿用我在 Bricks 中抓取 API 入门里讲的完全相同的逻辑,没读过的话建议先看那篇。先创建主函数:

function bl_get_movies_from_api(){
  $moviedb_api = 'https://api.themoviedb.org/3/movie/top_rated?api_key=' . TMDB_API_KEY . '&language=en-US';
  $response = wp_safe_remote_get($moviedb_api);
  // Stop the function if the response returns an error
  if ( is_wp_error( $response ) ) {
	return false;
  }
  // Get the body part 
  $response_body = wp_remote_retrieve_body( $response );
  // Decode the JSON object into a PHP array
  $response_body_decoded = json_decode( $response_body, true );
  return $response_body_decoded['results'];
}

此时我们拿到了一个包含每部电影数据的 PHP 数组,输出大致长这样:

array(20) {
  [0]=>
  array(14) {
    ["adult"]=>
    bool(false)
    ["backdrop_path"]=>
    string(32) "/rl7Jw8PjhSIjArOlDNv0JQPL1ZV.jpg"
    ["genre_ids"]=>
    array(2) {
      [0]=>
      int(10749)
      [1]=>
      int(18)
    }
    ["id"]=>
    int(851644)
    ["original_language"]=>
    string(2) "ko"
    ["original_title"]=>
    string(15) "20 Century Girl"
    ["overview"]=>
    string(377) "Yeon-du asks her best friend Bora to collect all the information she can about Baek Hyun-jin while she is away in the U.S. for heart surgery. Bora decides to get close to Baek's best friend, Pung Woon-ho first. However, Bora's clumsy plan unfolds in an unexpected direction. In 1999, a year before the new century, Bora, who turns seventeen, falls into the fever of first love."
    ["popularity"]=>
    float(314.92899999999997)
    ["poster_path"]=>
    string(32) "/od22ftNnyag0TTxcnJhlsu3aLoU.jpg"
    ["release_date"]=>
    string(10) "2022-10-06"
    ["title"]=>
    string(17) "20th Century Girl"
    ["video"]=>
    bool(false)
    ["vote_average"]=>
    float(8.8000000000000007)
    ["vote_count"]=>
    int(248)
  }
  [1]=> etc....

本教程会用到这些字段:

  • ['backdrop_path']:取图片链接,作为文章的特色图

  • ['title']:作为文章标题

  • ['id']:拼进文章 slug,保证文章唯一

  • ['overview']:作为文章摘要

  • ['release_date']、['vote_average']、['vote_count']:用来填充 ACF 自定义字段

创建自定义文章

现在我们已经有了包含全部电影信息的大数组。接下来要遍历数组、为每部电影创建自定义文章,不过先写一个处理文章 slug 的自定义函数:

function bl_slugify($text){
  // remove unwanted characters
  $text = preg_replace('~[^-\w]+~', '', $text);
  // trim
  $text = trim($text, '-');
  // remove duplicate -
  $text = preg_replace('~-+~', '-', $text);
  // lowercase
  $text = strtolower($text);
  if (empty($text)) {
    return 'n-a';
  }
  return $text;
}

这个函数负责替换特殊字符、用 - 分隔每个单词、把文本转成小写,没什么复杂的。

接下来遍历数组,向 CPT movie 创建自定义文章:

// Loop in each movie
foreach($response_body_decoded['results'] as $movie){
  // Slug
  $movie_slug = bl_slugify( $movie['title'] . '-' . $movie['id'] );     
  // Existing movie inside the CPT
  $existing_movie = get_page_by_path( $movie_slug, 'OBJECT', 'movie' );
  // Check if the movie post has already been created
  if( $existing_movie === null  ){
    // Create a new custom post
    $inserted_movie = wp_insert_post( [
      'post_name' => $movie_slug,
      'post_title' => $movie['title'],
      'post_excerpt' => $movie['overview'],
      'post_type' => 'movie',
      'post_status' => 'publish'
    ] );
    // Continue to the next loop iteration if the post was not correctly created
    if( is_wp_error( $inserted_movie ) || $inserted_movie === 0 ) {
      continue;
    }
  }
}

几点说明:

  • 生成 slug 时把电影标题和ID拼接起来,保证 slug 唯一。

  • 创建文章前先用 get_page_by_path() 检查 slug 是否已存在于自定义文章类型 movie 中——不存在(这正是我们想要的)时返回 null,然后才创建新文章——这样函数跑多次也不会重复建文章。

  • 用 WordPress 核心函数 wp_insert_post() 创建自定义文章,这个函数允许传数组参数来设置文章名(slug)、标题等。

  • 文章创建后检查是否处理正确:如果函数检测到错误,或返回的文章 ID 为 0,就跳过本次循环迭代,处理下一部电影。

设置特色图

从 API 下载图片、放进媒体库、设为文章特色图:

// Set Featured Image
$url = 'https://image.tmdb.org/t/p/original' . $movie['backdrop_path'];
$img = media_sideload_image( $url, $inserted_movie);//download image to wpsite from url
$img = explode("'",$img)[1];// extract http.... from <img src'http...'>
$attId = attachment_url_to_postid($img);//get id of downloaded image
set_post_thumbnail( $inserted_movie, $attId );//set the given image as featured image for the post

填充 ACF 字段

先在 ACF 里创建字段:

记得在「显示选项」(Screen Options)里勾选 字段键(Field Keys) 复选框。

现在把自定义字段和 API 值对应起来:箭头左边是 ACF 字段键,右边是 API 返回的数组值:

// Map ACF keys with the API values
$acf_fields = array(
   'field_6375ea77d3491' => 'release_date',
   'field_6375ea8fd3492' => 'vote_average',
   'field_6375ea9fd3493' => 'vote_count',
);

最后用 API 值更新每个字段:

// Update each ACF value
foreach( $acf_fields as $key => $name ) {
   update_field( $key, $movie[$name], $inserted_movie );
}

坚持住,快结束了。

启用 AJAX

思路是:通过一次 AJAX 调用运行我们的函数,一次性填充所有自定义文章。要启用 AJAX 调用并把函数当作回调,需要用到 wp_ajax 钩子:

add_action( 'wp_ajax_nopriv_get_movies_from_api', 'get_movies_from_api' );
add_action( 'wp_ajax_get_movies_from_api', 'get_movies_from_api' );

完整代码

好了,所有代码都齐了:

// Movies API
add_action( 'wp_ajax_nopriv_get_movies_from_api', 'get_movies_from_api' );
add_action( 'wp_ajax_get_movies_from_api', 'get_movies_from_api' );
function bl_get_movies_from_api(){
  define ('TMDB_API_KEY', '<YOUR_API_KEY>' );
  $moviedb_api = 'https://api.themoviedb.org/3/movie/top_rated?api_key=' . TMDB_API_KEY . '&language=en-US';
  $response = wp_safe_remote_get($moviedb_api);
  // Stop the function if the response returns an error
  if ( is_wp_error( $response ) ) {
	return false;
  }
  // Get the body part 
  $response_body = wp_remote_retrieve_body( $response );
  // Decode the JSON object into a PHP array
  $response_body_decoded = json_decode( $response_body, true );
  // Loop in each movie
  foreach($response_body_decoded['results'] as $movie){
    // Slug
    $movie_slug = bl_slugify( $movie['title'] . '-' . $movie['id'] );     
    // Existing movie inside the CPT
    $existing_movie = get_page_by_path( $movie_slug, 'OBJECT', 'movies' );
    // Check if the movie post has already been created
    if( $existing_movie === null  ){
      // Create a new custom post
      $inserted_movie = wp_insert_post( [
        'post_name' => $movie_slug,
        'post_title' => $movie['title'],
        'post_excerpt' => $movie['overview'],
        'post_type' => 'movie',
        'post_status' => 'publish'
      ] );
      // Continue to the next loop iteration if the post was not correctly created
      if( is_wp_error( $inserted_movie ) || $inserted_movie === 0 ) {
        continue;
      }
      // Set Featured Image
      $url = 'https://image.tmdb.org/t/p/original' . $movie['backdrop_path'];
      $img = media_sideload_image( $url, $inserted_movie);//download image to wpsite from url
      $img = explode("'",$img)[1];// extract http.... from <img src'http...'>
      $attId = attachment_url_to_postid($img);//get id of downloaded image
      set_post_thumbnail( $inserted_movie, $attId );//set the given image as featured image for the post
      // Map ACF keys with the API values
      $acf_fields = array(
        'field_6375ea77d3491' => 'release_date',
        'field_6375ea8fd3492' => 'vote_average',
        'field_6375ea9fd3493' => 'vote_count',
      );
      // Update each ACF value
      foreach( $acf_fields as $key => $name ) {
        update_field( $key, $movie[$name], $inserted_movie );
      }
    }
  }
}
function bl_slugify($text){
  // remove unwanted characters
  $text = preg_replace('~[^-\w]+~', '', $text);
  // trim
  $text = trim($text, '-');
  // remove duplicate -
  $text = preg_replace('~-+~', '-', $text);
  // lowercase
  $text = strtolower($text);
  if (empty($text)) {
    return 'n-a';
  }
  return $text;
}

相关阅读:

运行 AJAX 调用

简单的一步!进入 /wp-admin/,粘贴下面的 URL:your-domain.com/wp-admin/admin-ajax.php?action=bl_get_movies_from_api(把 your-domain 换成你自己的域名)。这会通过 admin-ajax.php 方式执行我们的 AJAX 调用。

然后等着就行。函数在后台运行,从 API 导入所有数据。

函数跑完后会返回 0,这完全正常。

来看看我们的自定义文章类型 movie!

所有电影都导入成功了!再看看文章数据:

所有内容都正确填充并保存。

恭喜,你刚刚用公开 API 把一批电影导入到了自定义文章类型里!

常见误区与性能提示

几个坑:一是一次性拉全量 API 数据同步,文章多时超时或内存爆,按本文用单 AJAX 调用分批/增量更稳;二是特色图没处理远程 URL 就直接塞,可能只存了外链不落本地,按本文把图下载成本地附件;三是 ACF 字段类型没对齐(文本/数组),填充后显示错。性能上,大批量导入放后台任务(cron/WP-CLI)比前台 AJAX 更稳,前台只做触发。接第三方 API 记得处理失败重试和限流。

延伸阅读

常见问题(FAQ)

一次性同步会超时吗? 会,文章多时按本文用单 AJAX 调用分批/增量,或放后台任务更稳。

特色图只存了外链? 要把远程图下载成本地附件,不然只是外链、源站没了图就挂。

ACF 字段填错? 确认字段类型和 API 返回结构一致(文本/数组),填错就显示异常。

大批量导入放哪跑? 放后台(cron/WP-CLI)比前台 AJAX 稳,前台只触发、后台慢慢跑。

Ray Chan

站长

Ray Chan

WordPress Developer & Bricks Specialist

WordPress developer with 10+ years of client builds. Switched to Bricks in 2023 — now builds fast WordPress sites and migrates legacy Elementor/Divi projects.

延伸阅读