dellblorin日記

袖擦り合うも他生の縁

メルカリのサイトから新着情報をPHPでスクレイピングする

PC内を整理していると2年前に書いた、メルカリの商品情報をWebサイトからスクレイピングして取得するPHPスクリプトが出てきた。登録したキーワードで検索し、取得した情報はテキストデータとして保存。以降取得データと過去データを突き合わせて新しいものがあればメールで送信。まだ動いたため記録しておく。

サンプルプログラムではkeywordに「雑誌」、sort_orderに「新着」を設定している。xampp環境で実行したがsendmailを使う場合、下記サイトが設定に役立った。

qiita.com

blog.saboh.net

レンタルサーバーを借りているならcronで動かせばいいが試していない。下記プログラムではwhile文を使い無限ループで定期実行させている。

スクレイピング処理はphpQueryという外部ライブラリを使用した。

<?php
/**
 * メルカリからHTMLをスクレイピング
 */
require_once("phpQuery-onefile.php");
ini_set("max_execution_time", 0);

$url = "https://www.mercari.com/jp/search/";    // 基本のURL
$sortOrder = "?sort_order=created_desc";        // 安い順:price_asc、高い順:price_desc、新着:created_desc、いいね:like_desc
$keyword = "&keyword=雑誌名等";                 // 検索ワード
$categoryRoot = "&category_root=";              // 親カテゴリ
$categoryChild = "&category_child=";            // 子カテゴリ
$priceMin = "&price_min=";                      // 最低価格
$priceMax = "&price_max=";                      // 最高価格
$parameter = urldecode($sortOrder . $keyword . $categoryRoot . $categoryChild . $priceMin . $priceMax);
$filename = "cache.json";
const LIMIT_ITEMS = 5;

while (true) {
    // スクレイピング開始
    $jsonScrRes = scraping($url . $parameter);

    // キャッシュの読み込み
    if(file_exists($filename)) {
        $jsonCache = mb_convert_encoding(file_get_contents($filename), 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
        $jsonCacheRes = json_decode($jsonCache, true);

        // 今回と前回の取得アイテムの比較
        checkNewItem($jsonScrRes, $jsonCacheRes, $filename);
    } else {
        // 新規データをセーブファイルにJSON形式で保存
        file_put_contents($filename, json_encode($jsonScrRes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        chmod($filename, 0666);
    }

    // 5分おき
    sleep(5 * 60);
}




/**
 * スクレイピング処理
 */
function scraping($url) {
    $agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0)";
    $header = array(
        'Accept-Encoding: deflate',
        'Accept-Language: ja,en-US;q=0.9,en;q=0.8',
    );
    $conn = curl_init();
    curl_setopt($conn, CURLOPT_URL, $url);
    curl_setopt($conn, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($conn, CURLOPT_USERAGENT, $agent);
    curl_setopt($conn, CURLOPT_HTTPHEADER, $header);
    $res = curl_exec($conn);
    curl_close($conn);

    $doc = phpQuery::newDocument($res);
    foreach ($doc["section"]->find(".items-box") as $entry){
        $link = pq($entry)->find("a")->attr("href");
        $title = pq($entry)->find("h3")->text();
        // jsonデータとして保持
        $jsonData[] = ['link' => $link, 'title' => $title];
    }

    return array_slice($jsonData, 0 , LIMIT_ITEMS);
}

/**
 * スクレイピング結果とキャッシュを比較し、新規アイテムのみ記録及びメールで通知。
 */
function checkNewItem($JsonScr, $jsonCache, $filename) {
    // 最初に新着アイテムをコピーし、キャッシュと重複するものは削除する
    $addItems = $JsonScr;
    $scrMax = count($JsonScr);
    $cacheMax = count($jsonCache);
    
    for ($i = 0; $i < $scrMax; $i++) {
        for ($j = 0; $j < $cacheMax; $j++) {
            // URLから出品商品のユニークIDの抜き出し
            $scrItemId = str_replace("/jp/items/", "", substr($JsonScr[$i]['link'], 0, strpos($JsonScr[$i]['link'], "/?")));
            $cacheItemId = str_replace("/jp/items/", "", substr($jsonCache[$j]['link'], 0, strpos($jsonCache[$j]['link'], "/?")));
            if (strcmp($scrItemId, $cacheItemId) == 0) {
                // 添字で配列を削除
                unset($addItems[$i]);
                continue;
            }
        }
    }

    // 歯抜けになった添字を詰める
    $addItems = array_values($addItems);

    if (0 < count($addItems)) {
        $tmp = array_reverse(array_merge(array_reverse($jsonCache), array_reverse($addItems)));
        $jsonWritedata = array_slice($tmp, 0, LIMIT_ITEMS);
        
        // 新規データをセーブファイルにJSON形式で保存
        file_put_contents($filename, json_encode($jsonWritedata, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

        sendMail($addItems);
    }
}

function sendMail($items) {
    $mercariUrl = 'https://www.mercari.com';
    $to = "メール送信先のアドレス";
    $subject = "メルカリで新着あり";
    $message = "";
    $max = count($items);
    for($i = 0; $i < $max; $i++) {
        $message .= "新着:" . ($i + 1) . "\n";
        $message .= $items[$i]["title"] . "\n";
        $message .= $mercariUrl . $items[$i]["link"] . "\n\n";
    }
    $headers = "From: メール送信先のアドレス";

    mb_language("Japanese");
    mb_internal_encoding("UTF-8");
    if (mb_send_mail($to, $subject, $message, $headers)) {
        echo "送信成功";
    } else {
        echo "送信失敗";
    }
}
?>

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング」を読んでpythonでも同じスクリプトを書いたが紛失。BeautifulSoup4を使ったと思う。