diff --git a/docs/en/shopping.md b/docs/en/shopping.md index e3f36ab0e..511fcc301 100644 --- a/docs/en/shopping.md +++ b/docs/en/shopping.md @@ -63,3 +63,15 @@ Transform any search into a feed. For instance, in https://www.leboncoin.fr/recherche/?**category=10&locations=Paris_75015**, the query is **category=10&locations=Paris_75015** + +## Mercari + +### Goods + + + +All categories, see [Category list](https://www.mercari.com/jp/category/) + +All brands, see [Brand list](https://www.mercari.com/jp/brand/) + + diff --git a/docs/shopping.md b/docs/shopping.md index 5305f8201..c30c55592 100644 --- a/docs/shopping.md +++ b/docs/shopping.md @@ -68,6 +68,18 @@ For instance, in +## Mercari + +### 商品 + + + +所有分类参见 [分类清单](https://www.mercari.com/jp/category/) + +所有品牌参见 [品牌清单](https://www.mercari.com/jp/brand/) + + + ## Westore ### 新品 diff --git a/lib/router.js b/lib/router.js index bd3280552..3928a1508 100644 --- a/lib/router.js +++ b/lib/router.js @@ -3241,6 +3241,9 @@ router.get('/appsales/:caty?/:time?', require('./routes/appsales/index')); // CGTN router.get('/cgtn/top', require('./routes/cgtn/top')); +// Mercari +router.get('/mercari/:type/:id', require('./routes/mercari/index')); + // notefolio router.get('/notefolio/:caty?/:order?/:time?/:query?', require('./routes/notefolio/index')); diff --git a/lib/routes/mercari/index.js b/lib/routes/mercari/index.js new file mode 100644 index 000000000..f6803b005 --- /dev/null +++ b/lib/routes/mercari/index.js @@ -0,0 +1,68 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); + +module.exports = async (ctx) => { + ctx.params.type = ctx.params.type === 'search' ? ctx.params.type + '/?keyword=' : ctx.params.type + '/'; + + const rootUrl = 'https://www.mercari.com'; + const currentUrl = `${rootUrl}/jp/${ctx.params.type}${ctx.params.id}`; + const response = await got({ + method: 'get', + url: currentUrl, + }); + + const $ = cheerio.load(response.data); + + let list; + if (ctx.params.type === 'category/') { + list = JSON.parse(response.data.match(/"searchCategory":(.*?),"crossBorderBanner/)[1]) + .data.slice(0, 10) + .map((item) => ({ + title: item.name, + link: `${rootUrl}/jp/items/${item.id}/`, + })); + } else { + list = $('section.items-box') + .slice(0, 10) + .map((_, item) => { + item = $(item); + return { + title: item.find('h3.items-box-name').text(), + link: `${rootUrl}${item.find('a').attr('href')}`, + }; + }) + .get(); + } + + const items = await Promise.all( + list.map( + async (item) => + await ctx.cache.tryGet(item.link, async () => { + try { + const detailResponse = await got({ + method: 'get', + url: item.link, + }); + + const content = cheerio.load(detailResponse.data); + + content('div.item-btn-float-area').remove(); + content('div.owl-nav').remove(); + content('div.owl-dots').remove(); + + item.description = content('div.item-main-content').html(); + + return item; + } catch (e) { + return Promise.resolve(''); + } + }) + ) + ); + + ctx.state.data = { + title: $('title').text(), + link: currentUrl, + item: items, + }; +};