【検証してみた】株式会社ゆめみは、Organization対抗戦で不正をしているのか？

# 今年も盛り上がっている Qiita Engineer Festa！

**Qiita Engineer Festa 2023**、大いに盛り上がっていますね・・・！

特に、**Organization対抗戦・・・！**

いろんな企業さんが、必死になって沢山の記事を書いています。

# 現在のランキングはこちら

### Organization対抗戦: 総合いいね賞ランキング

※7月21日 12:35時点のランキングです。


![スクリーンショット 2023-07-21 12.34.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/c9f95ddb-61f0-3948-c0f5-20f74be4f6a4.png)


なんと、弊社（株式会社ゆめみ）も**2位**につけています。

# ただ、気になったことが・・・

ワイ「なんか、ワイが記事を書くと・・・」
ワイ「いつも、同僚たちが沢山いいねをくれる・・・」
ワイ「そのおかげで、いつもデイリートレンドに載ることができてる・・・」
ワイ「これって、ちょっとズルくないか？」
ワイ「**組織票**ってやつとちゃうか・・・？」

# 調べてみた

デイリートレンドの30記事を対象に「同僚からのいいね[^1]が何％を占めているのか」を調べるブックマークレットを作成してみました！

### コード
<details>
<summary>ブックマークレット用JavaScript（長いので折りたたんであります）</summary>

```js:ブックマークレット用JavaScript
javascript: (async () => {
  const cache = {};
  const addQueryToUrl = (url, params) => {
    const urlObj = new URL(url);
    const paramsObj = new URLSearchParams(urlObj.search.slice(1));

    for (const key in params) {
      paramsObj.set(key, params[key]);
    }

    urlObj.search = paramsObj.toString();

    return urlObj.toString();
  };

  const getPageDoc = async (link, query = {}) => {
    const url = addQueryToUrl(link, query);
    if (url in cache) return cache[url];

    await sleep(500);
    const response = await fetch(url);
    const data = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(data, 'text/html');
    cache[url] = doc;
    return doc;
  };

  const sleep = (ms) => new Promise((res) => setTimeout(res, ms));

  const articles = Array.from(document.querySelectorAll("main article"));

  const getAllPageIds = async (pageUrl, querySelectorString) => {
    let page = 1;
    let hasPage = true;
    let allIds = [];

    while (hasPage) {
      const doc = await getPageDoc(pageUrl, { page });
      const docIds = Array.from(doc.querySelectorAll(querySelectorString)).map(({ href }) => href.replace("https://qiita.com/", ""));
      allIds = [...allIds, ...docIds];
      hasPage = docIds.length !== 0;
      page++;
    }

    return allIds;
  };

  const getOrgMemberIds = async (orgMembersPageHref) => {
    return await getAllPageIds(orgMembersPageHref, "main ul li > a")
  };

  const getLikerIds = async (likersPageHref) => {
    return await getAllPageIds(likersPageHref, "[id^=PersonalArticleLikersPage] li > a")
  };

  const getSelfLikeIds = (likerIds, orgMemberIds) => {
    return likerIds.filter(likerId => orgMemberIds.includes(likerId));
  };

  for (const article of articles) {
    const orgPageLink = article.querySelector("[href^='/organizations']");
    if (!orgPageLink) continue;

    const messageFrame = document.createElement("div");
    messageFrame.classList.add("messageFrame");
    messageFrame.style.position = "relative";
    messageFrame.style.border = "1px solid #aaa";
    messageFrame.style.backgroundColor = "#aaa";
    messageFrame.style.color = "white";
    messageFrame.style.marginTop = "10px";
    messageFrame.style.padding = "5px 10px";
    messageFrame.style.fontWeight = "bold";
    messageFrame.innerHTML = "組織票を集計中...（数十秒かかります）";
    article.appendChild(messageFrame);
  }

  for (const article of articles) {
    const orgPageLink = article.querySelector("[href^='/organizations']");
    if (!orgPageLink) continue;

    const orgPageHref = orgPageLink.href;
    const orgMembersPageHref = `${orgPageHref}/members`;
    const orgMemberIds = await getOrgMemberIds(orgMembersPageHref);
    const articleLink = article.querySelector("main article > a");
    if (!articleLink) continue;

    const articleLikersHref = `${articleLink.href}/likers`;
    const likerIds = await getLikerIds(articleLikersHref);
    const selfLikeIds = getSelfLikeIds(likerIds, orgMemberIds);
    const percent = Math.round(selfLikeIds.length / likerIds.length * 100);

    const message = document.createElement("div");
    message.style.position = "relative";
    message.style.zIndex = "1";
    message.innerHTML = `組織票: ${selfLikeIds.length} 票（${percent} %）`;
    const meter = document.createElement("div");
    meter.style.position = "absolute";
    meter.style.transition = "width 0.5s";
    meter.style.backgroundColor = "red";
    meter.style.opacity = "0.3";
    meter.style.height = "100%";
    meter.style.left = "0";
    meter.style.top = "0";
    meter.style.width = "0%";

    const messageFrame = article.querySelector(".messageFrame");
    if (!messageFrame) continue;

    messageFrame.style.border = "1px solid #ffbdbd";
    messageFrame.style.backgroundColor = "transparent";
    messageFrame.style.color = "black";
    messageFrame.innerHTML = "";
    messageFrame.appendChild(meter);
    messageFrame.appendChild(message);
    setTimeout(() => meter.style.width = `${percent}%`, 100);
  };
})();
```
</details>

### ~~ブックマークレットの登録方法~~

1. ~~ブラウザで適当なページをブックマークする~~
1. ~~そのブックマークの編集画面を開く~~
1. ~~URLの入力欄に、上記のJavaScriptコードを貼り付ける~~
1. ~~ブックマークを保存する~~

### ~~ブックマークレットの実行方法~~
1. ~~[**Qiitaのデイリートレンドページ**](https://qiita.com/trend)を開く~~
1. ~~上で作ったブックマークをクリックする~~
1. ~~数十秒待つ（サイトに負荷がかからないように、ゆっくりの挙動にしてあります）~~

#### 追記（2024/12/15）

スクレイピングは許可されていないとのことです。
実行しないでください🙇

[Qiita API・スクレイピングについて
](https://help.qiita.com/ja/articles/qiita-api)

# 検証してみる

以下が実行結果のスクショ（抜粋）です。

![スクリーンショット 2023-07-21 12.53.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/14f6032c-b8fd-7054-6c06-98b018838ed6.png)
![スクリーンショット 2023-07-21 12.53.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/38de270a-5a7e-5797-f0e0-1646c8963ed8.png)

# 検証結果

**やっていた。[^2]**

今日のデイリートレンドには、ワイの記事が2つ載っていましたが、それぞれ───

- 14票（23%）
- 12票（34%）

↑このような組織投票がされていました。

デイリートレンドの他の記事たちも、かなり組織票が多かったです。

同僚からのいいねが **75%** という記事もありました。
（日によっては、100%組織票でトレンド入りしている記事も…）

# 何が問題なのか

- 組織票でいいねを稼いだ記事たちが、デイリートレンドを埋め尽くしてしまう
    - 本当に人気のある記事が、デイリートレンドに載らなくなってしまう
    - 普段から熱心に記事を書いている人が、トレンドから弾き出されてしまう
- デイリートレンドが「同僚からいいねしてもらった人ランキング」になっている


現在はOrganization対抗戦が開催中ということもあり、特に組織票が多いようです。

# Qiitaへの提言

**同Organization同士でのいいねは、ノーカウントにしませんか？**

[Qiitaさんのミッション](https://corp.qiita.com/about/)は「エンジニアを最高に幸せにする」のはずです。

こんな八百長ランキングを見せられても、誰も幸せになりません。

また、[Qiitaのガイドライン](https://help.qiita.com/ja/articles/qiita-community-guideline)には、こう書いてあります。

> エンジニアにとって再利用性・汎用性の高い情報が集まる場をつくろう

「八百長いいねが集まる場をつくろう」ではないはずです。

# 最後に

仲間が頑張って書いた記事に「いいね」をすることは、何も悪いと思いません。
僕も、同僚の記事には積極的に「いいね」をします。

あと「絶対にOrganization対抗戦で優勝してやる！」という気持ちも、悪いと思いません。

そういった気持ち自体は、人生の中で大事なものだと思います。
ですが、その結果として取るべき行動は───

> 「みんながいいねを押したくなるような記事を書いてやる！」

↑こうあるべきだと思います。

> 「同僚同士でいいね押しまくってやる！」

↑こうあるべきではないと思います。

[^1]: ここでの「同僚からのいいね」とは「投稿者自身が所属しているOrganizationのメンバーからのいいね」を指しています

[^2]: 組織票がルールで禁止されている訳ではないので、不正と呼ぶかどうかは微妙ですが...