【GAS、Google Spreadsheet】自分がブログで書いた記事のURLからスクレイピングしてドキュメントに出力する

1117 語
6 分
【GAS、Google Spreadsheet】自分がブログで書いた記事のURLからスクレイピングしてドキュメントに出力する

はじまり#

135ml avatar
135ml
バックアップは大事です。
リサちゃん avatar
リサちゃん
意味深か?

ツールの概要#

ツールの概要をざっと紹介します。

僕はGoogleドライブの所定のフォルダに今まで書いたブログの記事の下書きを管理しているのですが、たまに下書きを書かずにWordPress上で公開まで行ってしまう記事があります。(例えば、この記事は「20211103」のIDが付いているのですが、その記事の下書きはありません。)

その際にこのツールが実行されると、Googleスプレッドシートで管理している台帳で公開済みになっている記事の内容を読み取って、Googleドキュメントとして出力してくれます。

ツールのソース#

ツールのソースはこんな感じです。

var draftFolderId = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
function getAlreadyPostedList(postedList) {
// declare for prepare.
var alreadyPostedList = [], alreadyPostedChr = '済';
// get list of aticle already posted.
postedList.forEach(function(value) {
if(value[1] == alreadyPostedChr) {
alreadyPostedList.push([value[0], value[2]]);
}
});
return alreadyPostedList;
}
function readGssColumns() {
// declare for prepare.
var ss,
sheetName = 'ブログ_記事',
sheet,
postedNumber,
row_for_postedNumber = 6,
column_for_postedNumber = 13;
ss = SpreadsheetApp.getActive();
sheet = ss.getSheetByName(sheetName);
postedNumber = Number(sheet.getRange(row_for_postedNumber, column_for_postedNumber).getValue());
// declare for get list from GSS.
var dateList = [],
dateList_formated = [],
permalinkList = [],
permalinkList_formated = [],
postedList = [],
i = 2, // index of row to start reading sheet.
column_for_date = 3, // index of 「起票日」column.
column_for_posted = 6, // index og 「投稿済」column.
column_for_permalink = 7, // index of 「パーマリンク」column.
returnList = [];
// get dateList and cleansing.
dateList = sheet.getRange(i, column_for_date, postedNumber, 1).getValues();
for (let j = 0; j < postedNumber; j++) {
dateList_formated.push(Utilities.formatDate(dateList[j][0], 'JST', 'yyyyMMdd'));
}
// get permalinkList and cleansing.
permalinkList = sheet.getRange(i, column_for_permalink, postedNumber, 1).getValues();
for (let j = 0; j < postedNumber; j++) {
permalinkList_formated.push(permalinkList[j][0]);
}
// get postedList.
postedList = sheet.getRange(i, column_for_posted, postedNumber, 1).getValues();
// create returnList
for (let k = 0; k < postedNumber; k++) {
returnList.push([dateList_formated[k], postedList[k][0], permalinkList_formated[k]]);
}
return returnList;
}
function readYetReadArticles(postedList) {
// declare for prepare.
var alreadyPostedList = getAlreadyPostedList(postedList);
// declare for prepare.
var yetReadList = getYetReadList(alreadyPostedList);
return yetReadList;
}
function exportYetReadArticles(yetReadArticlesList) {
// declare.
var targetUrl = 'https://www.endorphinbath.com/',
getUrl,
html,
articleTitle,
articleText,
editFile,
editFileId,
docFile,
body_docFile,
docFileName,
endOfText = '<p>以上になります!</p>\n </div>',
errorDocFile,
body_errorDocFile,
today = new Date();;
// テンプレートファイル(「yyyyMMdd(E)」)
var templateFile = DriveApp.getFileById('YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY');
// 出力先フォルダ
var outputFolder = DriveApp.getFolderById(draftFolderId);
// 「ErrorLog_Batch」Docファイル
errorDocFile = DocumentApp.openById('ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ');
body_errorDocFile = errorDocFile.getBody();
var paragraph = body_errorDocFile.appendParagraph(Utilities.formatDate(today, 'JST', 'yyyyMMdd') + '---ErrorLog---\n');
// Read articles not read yet and which draft is nothing.
yetReadArticlesList.some(function(value) {
getUrl = targetUrl + value[1];
html = UrlFetchApp.fetch(getUrl).getContentText('UTF-8');
// Get articleTitle and cleansing.
articleTitle = String(Parser.data(html).from('<h1 class="entry-title" itemprop="headline">').to('</h1>').iterate());
articleTitle = articleTitle.replace(/\n /g, '');
articleTitle = articleTitle.replace(/ /g, '');
// Get articleText and cleansing.
articleText = String(Parser.data(html).from('<div class="entry-content cf" itemprop="mainEntityOfPage">').to(endOfText).iterate());
articleText = articleText.replace(/p>\n\n\n\n<p>/g, 'p>\n<p>');
// Check whether scraping is correctly.
docFileName = value[0] + '_' + articleTitle;
if (articleText.indexOf('<h2><span id="toc1">はじまり</span></h2>') != -1) {
// Copy document with articleTitle.
editFile = templateFile.makeCopy(docFileName, outputFolder);
editFileId = editFile.getId();
docFile = DocumentApp.openById(editFileId);
body_docFile = docFile.getBody();
// And write articleText.
body_docFile.clear(); // 全消去
var paragraph = body_docFile.appendParagraph(articleText);
console.log(editFileId);
}else{
var paragraph = body_errorDocFile.appendParagraph(docFileName + '\n');
}
});
var paragraph = body_errorDocFile.appendParagraph('\n\n');
}
function getYetReadList(alreadyPostedList) {
// declare for prepare.
var yetReadList = [];
// declare for prepare.
var folder_id = draftFolderId, // Folder ID of draft for article.
folder,
files,
file_now,
date_format_8 = "yyyyMMdd";
// get list of folders.
folder = DriveApp.getFolderById(folder_id);
// get list of aticle not read yet.
alreadyPostedList.forEach(function(value) {
files = folder.getFiles();
while(files.hasNext()) {
file_now = files.next();
// If draft of article exitsts, break while loop.
if (value[0] == file_now.getName().substring(0, date_format_8.length)) {
break;
}
// If draft of article is nothing, ...
if (files.hasNext() == false) {
// If Permalink is not null, ...
if (value[1] != "") {
// Push Article into list.
yetReadList.push([value[0], value[1]]);
}
}
}
});
return yetReadList;
}
function exportPostedArticles() {
// declare for execute.
var postedList;
postedList = readGssColumns();
yetReadArticlesList = readYetReadArticles(postedList);
exportYetReadArticles(yetReadArticlesList);
}

ツール作成時のハイライト#

1. iteratorの初期化を忘れてハマった。#

実際にハマった部分がここです。

// .... (abbreviate) ....
files = folder.getFiles();
// get list of aticle not read yet.
alreadyPostedList.forEach(function(value) {
while(files.hasNext()) {
file_now = files.next();
// If draft of article exitsts, break while loop.
if (value[0] == file_now.getName().substring(0, date_format_8.length)) {
break;
}
// If draft of article is nothing, ...
if (files.hasNext() == false) {
// If Permalink is not null, ...
if (value[1] != "") {
// Push Article into list.
yetReadList.push([value[0], value[1]]);
}
}
}
});
return yetReadList;

return yetReadList;でどう足掻いても要素が1つしか出力されず、その解析に時間が掛かりました・・・。

原因は、filesがwhile文の中のiteratorの役割になっていますが、そのiteratorをforEach文の外で代入していたのが、そのバグの原因でした。そのせいで、for文が2回目に入る時に、iteratorが再代入されなかったのです。

先程貼った全ソースの中では、iteratorの代入文をforEachループの中に入れたので、解決しています。

2. ParserをGAS上のライブラリに追加する。#

今回、GASで初めてスクレイピングを導入して、GAS上の「ライブラリ」という部分を初めて触りました。

「ファイル」「コード.gs」「ライブラリ」「サービス」と項目が並んでいるので、その右側の「+」ボタンをクリックして、ライブラリの追加をしました。スクリプトIDは「1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw」で、検索するとこんな風に表示されるのでその状態で「追加」をクリックします。

おしまい#

135ml avatar
135ml
おーし、網羅できたぞー。とりあえず、下書き云々カンヌンを気にせずに執筆できるようになりましたとさ。
リサちゃん avatar
リサちゃん
以上になります!
135ml avatar
135ml
おい! やめろ!! スクレイピングに使ってる識別文字列の一部を無闇に使うんじゃねえ!!
リサちゃん avatar
リサちゃん
へっ?

以上になります!

記事を共有

この記事が役に立ったなら、ぜひ他の人と共有してください!

【GAS、Google Spreadsheet】自分がブログで書いた記事のURLからスクレイピングしてドキュメントに出力する
https://endorphinbath.com/posts/gas-export-from-article/
著者
kinkinbeer135ml
公開日
2021-12-25
ライセンス
CC BY-NC-SA 4.0
関連記事 スマート
1
【GAS、Google Spreadsheet】自分がブログで書いた記事のURLからスクレイピングしてドキュメントに出力する
Code 下書きを書かずに投稿してしまったブログの記事をスクレイピングして、Googleドライブ上に出力するツールを作りました。スクレイピングにエラーにならずに失敗した時のログを出力する処理も施しています。
2
【GAS、Google Spreadsheet】ブログに使用した画像をGoogleドライブで管理するために書いたスクリプト
Code 僕は、既に公開した記事で使用したファイルをGoogleドライブの所定のフォルダに保存しています。その画像を完了済みのフォルダに定期的に移動してくれるスクリプトを作りました。
3
【GAS、Google Spreadsheet】Google Driveに共有したWebサイトのURLをGoogleスプシに転記する。
Code Googleドライブに共有したWebサイトのURLが載ったテキストファイルを読み込んで、Googleスプシに転記するツールを作りました。後で読みたいと思った記事を一元管理できて便利に使えています。
4
【GAS、Google Spreadsheet】Googleドライブのフォルダに有るファイルを一覧で取得するスクリプトです
Code GoogleDriveの指定のフォルダにあるファイルを一覧で取得します。Googleドライブを整理したい時に役立つツールになるかと思います。
5
【GAS、Google Spreadsheet】Googleドライブのルートフォルダに有るフォルダを一覧で取得するスクリプトです
Code GoogleDriveのルートフォルダにあるフォルダを一覧で取得します。Googleドライブを整理したい時に役立つツールになるかと思います。
ランダム記事 ランダム
Profile Image of the Author
kinkinbeer135ml
SIerをやめて、プログラミングを勉強しています。※Amazonアソシエイトに参加しています。
お知らせ
私のブログへようこそ!これはサンプルのお知らせです。
音楽
カバー

音楽

再生中なし

0:00 0:00
歌詞なし
カテゴリ
タグ
サイト統計
記事
287
カテゴリー
8
タグ
93
総文字数
486,174
運用日数
0
最終活動
0 日前

目次