E2Eテストの練習としてPuppeteerで実用的なコードを書いてみた。
やったこと
FANZA同人のランキング1〜100位の作品を取得し、全てのジャンルを計測して 人気ジャンル を出してみた。
www.dmm.co.jp
const puppeteer = require('puppeteer');
let browser;
beforeEach(async () => browser = await puppeteer.launch());
afterEach(async () => await browser.close());
const timeout = 20 * 60 * 1000;
displayRanking = async (page) => {
for(let i = 1; i < 5; i++) {
await page.evaluate(_ => window.scrollBy(0, document.body.scrollHeight));
await page.waitFor(`.rank-rankListItem:nth-child(${20 * (i + 1)})`);
}
};
findTags = async (page, urls) => {
const tagsArray = [];
for(let i = 0; i < urls.length; i++) {
console.log(`${i}: parsing... ${urls[i]}`);
await page.goto(urls[i]);
const tags = await page.$$eval('.genreTagList__item', divs => divs.map(div => div.innerText));
tagsArray.push(tags);
}
return tagsArray.flat();
};
test('doujin', async () => {
const page = await browser.newPage();
await page.goto('https://www.dmm.co.jp/dc/doujin/-/ranking-all/=/sort=popular/term=monthly/');
const title = await page.$eval('.c_hdg_withSortTitle', el => el.innerText);
expect(title).toMatch(/総合ランキング 月間/);
await displayRanking(page);
const itemsCount = await page.$$eval('.rank-rankListItem', items => items.length);
expect(itemsCount).toBe(100);
const urls = await page.$$eval('.rank-name a', links => links.map(link => link.href));
const tags = await findTags(page, urls);
const ranks = {};
tags.forEach((tag) => {
if (ranks[tag] === undefined) ranks[tag] = 0;
ranks[tag] += 1;
});
ranks['成人向け'] = 0;
ranks['男性向け'] = 0;
const topRanks = Object.entries(ranks).sort((a, b) => b[1] - a[1]).slice(0, 20);
console.log(topRanks);
}, timeout);
無限スクロール
最初は1〜20位までしか表示されていないので、スクロールしつつ waitFor
で作品の表示を待つようにする。
displayRanking = async (page) => {
for(let i = 1; i < 5; i++) {
await page.evaluate(_ => window.scrollBy(0, document.body.scrollHeight));
await page.waitFor(`.rank-rankListItem:nth-child(${20 * (i + 1)})`);
}
};
各ページのタグ取得
タブを生成して、並列に処理しようとしたら TimeoutError: Navigation Timeout Exceeded: 30000ms exceeded
が起きて、解決方法が分からなかった。
とりあえず、1ページずつ順番に処理する方法で対応。(時間かかるけど)
findTags = async (page, urls) => {
const tagsArray = [];
for(let i = 0; i < urls.length; i++) {
console.log(`${i}: parsing... ${urls[i]}`);
await page.goto(urls[i]);
const tags = await page.$$eval('.genreTagList__item', divs => divs.map(div => div.innerText));
tagsArray.push(tags);
}
return tagsArray.flat();
};
ランキング計測
Rubyのgroup_byみたいなのが見当たらなかったので、それっぽくカウントした。
成人向けと男性向けは全ての作品に入っていたので除外してる。
const ranks = {};
tags.forEach((tag) => {
if (ranks[tag] === undefined) ranks[tag] = 0;
ranks[tag] += 1;
});
ranks['成人向け'] = 0;
ranks['男性向け'] = 0;
const topRanks = Object.entries(ranks).sort((a, b) => b[1] - a[1]).slice(0, 20);
結果
こんなジャンルが人気あるみたいですよ。
console.log test/dmm.test.js:52
[
[ '中出し', 77 ],
[ '巨乳', 63 ],
[ 'フェラ', 56 ],
[ '新作', 47 ],
[ 'おっぱい', 44 ],
[ '制服', 36 ],
[ 'パイズリ', 35 ],
[ '寝取り・寝取られ・NTR', 27 ],
[ '準新作', 22 ],
[ '処女', 20 ],
[ 'アナル', 20 ],
[ '人妻・主婦', 19 ],
[ '野外・露出', 18 ],
[ 'ラブラブ・あまあま', 16 ],
[ '辱め', 14 ],
[ 'ぶっかけ', 14 ],
[ '近親相姦', 13 ],
[ '和姦', 11 ],
[ '3P・4P', 11 ],
[ 'ハーレム', 11 ]
]
PASS test/dmm.test.js (687.794s)
✓ doujin (686607ms)