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,
+ };
+};