LOADING
import axios from 'axios';
import * as cheerio from 'cheerio';
const client = axios.create({
baseURL: 'https://cookpad.com',
timeout: 15000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept-Language': 'id-ID,id;q=0.9,en;q=0.8'
}
});
export async function search(query, page = 1) {
const { data } = await client.get(`/id/cari/${encodeURIComponent(query)}`, {
params: page > 1 ? { page } : undefined
});
const $ = cheerio.load(data);
const results = [];
$('#search-recipes-list > li[id^="recipe_"]').each((_, el) => {
const $el = $(el);
const id = ($el.attr('id') || '').replace('recipe_', '');
if (!id) return;
const title = $el.find('a.block-link__main').first().text().trim().replace(/\s+/g, ' ');
if (!title) return;
const author = $el.find('img[alt][title]').last().attr('alt')
|| $el.find('span.break-all span').last().text().trim()
|| null;
const duration = $el.find('.mise-icon-time').parent().find('.mise-icon-text').first().text().trim() || null;
const servings = $el.find('.mise-icon-user').parent().find('.mise-icon-text').first().text().trim() || null;
const ingredientsPreview = [];
const ingText = $el.find('[data-ingredients-redesign-target="ingredients"] .line-clamp-2').text() || '';
ingText.split('•').forEach(t => {
const clean = t.trim();
if (clean && clean.length > 1) ingredientsPreview.push(clean);
});
const image = $el.find('img[src*="cpcdn.com/recipes"]').attr('src') || null;
results.push({
id,
title,
author,
duration,
servings,
ingredientsPreview: ingredientsPreview.slice(0, 8),
image,
url: `https://cookpad.com/id/resep/${id}`
});
});
return results;
}
export async function detail(idOrUrl) {
const id = String(idOrUrl).match(/(\d+)/)?.[1] || idOrUrl;
const { data } = await client.get(`/id/resep/${id}`);
const $ = cheerio.load(data);
let recipe = null;
$('script[type="application/ld+json"]').each((_, el) => {
try {
const json = JSON.parse($(el).html());
if (json['@type'] === 'Recipe') recipe = json;
} catch {}
});
if (!recipe) throw new Error('Recipe not found');
return {
id,
title: recipe.name,
description: recipe.description || '',
image: Array.isArray(recipe.image) ? recipe.image[0] : recipe.image,
author: recipe.author?.name || '',
authorUrl: recipe.author?.url || '',
yield: recipe.recipeYield || '',
cuisine: recipe.recipeCuisine || '',
datePublished: recipe.datePublished || '',
ingredients: (recipe.recipeIngredient || []).map(i => i.trim()).filter(Boolean),
steps: (recipe.recipeInstructions || []).map(s => {
let images = [];
if (Array.isArray(s.image)) images = s.image;
else if (s.image) images = [s.image];
return {
text: s.text || s,
images
};
}),
url: `https://cookpad.com/id/resep/${id}`
};
}
// CLI
const isMain = process.argv[1] && (
import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/')) ||
process.argv[1].endsWith('cookpad.js')
);
if (isMain) {
const args = process.argv.slice(2);
(async () => {
try {
if (args[0] === 'detail' && args[1]) {
console.log(JSON.stringify(await detail(args[1]), null, 2));
} else if (args[0]) {
console.log(JSON.stringify(await search(args[0]), null, 2));
} else {
console.log('Usage:');
console.log(' node cookpad.js "nasi goreng"');
console.log(' node cookpad.js detail 26390975');
}
} catch (e) {
console.error(e.message);
process.exit(1);
}
})();
}bashnpm install axios cheerio
bashnode cookpad.js "nasi goreng" node cookpad.js detail 26390975 node cookpad.js detail "https://cookpad.com/id/resep/26390975"
bashimport { search, detail } from './cookpad.js'; const list = await search('nasi goreng'); const resep = await detail(list[0].id);