Habrahabr в PDF-варианте для электронной книги

Часто зависая на Хабре и не только много раз ловил себя на мысли, что информация и статьи гораздо эффективнее воспринимаются с телефона или планшета, когда читаешь в удобной позе, или даже не дома — в транспорте, командировках, и т.п. Описание игр с напильником для оригинальной конвертации Хабрахабра в PDF-вариант для комфортного оффлайн чтения на электронной книге — скорее любопытный вариант эксперимента, где задействовано сразу несколько интересных сервисов и известных всем технологий: PHP, CURL, ajax, js, css.

Итак, обо всем по-порядку.

Давно зрела мысль приобрести электронную книгу на e-ink чернилах. Всем известны доводы в пользу в этого: меньше нагрузка на глаза, дольше время работы на одной зарядке. Не приветствуя «комбайны» а-ля «все в одном», остановился на Amazon Kindle 6 версии, которая благодаря одной популярной доске объявлений досталась мне почти по цене как на ebay, зато сразу и с возможностью поторговаться и пощупать. Обзоров данной модели в вебе предостаточно, однако главная суть — в достаточном консерватизме производителя. Да, это электронная книга во всем ее проявлении. Тут нет mp3-плеера, приложений-игр и прочих фишек. Строго популярные мировые форматы «оцифровки макулатуры» и простенький браузер, не более того. Отмечу, что первоначальная ставка на встроенный браузер оказалась явно завышенной. И тот же Хабр открывался в очень мелком варианте шрифта, рендерил «хабровские» цвета заголовков #b5b5b5 в очень неконтрастный цвет. В общем, чтение напрямую, из браузера, немного напрягало.



К тому же, в отличие от скачанных книг, перелистывающихся на следующую страницу одним кликом, в браузере листать приходилось стандартным «телефонным» методом, проводя пальцем снизу-вверх. И если на каленом матером стекле телефона такие жесты уже как-то не замечаешь, то нежный, ранимый и слегка шершавый e-ink дисплей как-то не поднималась рука тереть несколько часов в день. К тому же, еще и вслепую, ведь картинка перерисовывалась только при отпускании пальца и глаз вынужден был искать место, откуда продолжать читать.

Тогда родилась идея запрограммировать некий «прокси» для удобного серфинга и экспорта наиболее интересного в PDF. Довольно быстро я написал PHP-прослойку, на вход которой в GET-параметре передавался url целевого сайта и которая через CURL запрашивала необходимый ресурс.

Первые наброски кода
$q = $_GET['q'];
if ( get_magic_quotes_gpc()) $q=addslashes(stripslashes(trim($q)));

if ($ch = curl_init()) { 
	// Инициализация параметров CURL
	curl_setopt($ch, CURLOPT_HEADER, false); 
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); 
	curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');
	curl_setopt($ch, CURLOPT_URL, $q );
	$p = curl_exec($ch);
	// Вывод в браузер
	header("Content-Type: text/html; charset=utf-8");		
	echo $p;
} else echo 'Ошибка инициализации CURL!';


Такое простейшее решение было успешно опробовано на ПК и в книге. Разумеется, все css-стили и js-скрипты благополучно «потерялись», т.к. этот простейший код не учел относительных ссылок в html-коде сайта, подвергаемого парсингу.

Для исправления этой досадной оплошности мне пришлось внедрить
замену относительных ссылок на абсолютные:
$p = str_replace ('href=\'/',	'href=\'http://m.habrahabr.ru/' , $p);
$p = str_replace ('href="/', 	'href="http://m.habrahabr.ru/' , $p);
$p = str_replace ('src=\'/',	'src=\'http://m.habrahabr.ru/' , $p);
$p = str_replace ('src="/', 	'src="http://m.habrahabr.ru/' , $p);

Обращаю внимание на парные замены с одинарными и двойными кавычками — в коде Хабрастраниц встречались оба варианта написания.

Останавливаться уже не хотелось и я стал думать над усовершенствованием своей задумки.

Были сформулированы следующие пожелания к повышению юзабилити веб-серфинга:
  1. Масштаб (для комфортного чтения нужно было увеличивать размер шрифтов в 2 раза);
  2. Цвет (в силу особенностей цветопередачи e-ink все «модные» цвета нужно было сделать поконтрастнее);
  3. Удобный скролл страницы.

Все коррекции я решил производить заменой, аналогичной вышеприведенной манипуляции с подменой ссылок.

Внедрение дополнений к CSS
// Увеличиваем масштаб страницы в целом, и задаем черный цвет для наибольшей контрастности чтения
$p = str_replace ('</head>', '<style>body { zoom:2; } * {color: #000 !important; }</style></head>', $p);


Конструкторская мысль не стоит на месте — эксперименты с удобным скроллингом я решил начать с простейшей кнопки, которая будет жестко привязана к нижнему правому углу экрана. По задумке, клик на нее должен был вызвать простейшую функцию, которая через scrollBy двигала бы скролл на нужное значение вниз. Промежуточных опробованных кодов приводить не буду. Увы и ах, все эти примеры, блестяще работающие на обычном ПК, не были работоспособны в киндлобраузере… Все известные кроссбраузерные функции, хотя бы просто дающие значение текущего скролла, выдавали undefined или 0 при тестах на девайсе. Более того, даже сама кнопка, имеющая четко заданное позиционирование fixed и привязанная к низу экрана, в книге скроллилась вместе с сайтом. Потратив полчаса времени (и пропорциональное моему рейту количество нервов в час * 0.5), я решил пойти другим путем. Который, как оказалось, вполне работоспособен и удобен.

А именно — мне захотелось сделать удобную возможность экспорта в PDF, чтобы вообще не отвлекаться на особенности браузера книги — и спокойно читать, благо делать это из файла намного комфортнее. К тому же оффлайн чтение актуально там, где нет wi-fi.

Помониторив текущие ресурсы быстрого экспорта веб-страниц в PDF ( selectpdf.com и web2pdfconvert.com), я переназначил функцию своей fixed-кнопки на такой быстрый экспорт.

Внедрение кнопки для быстрого скачивания страницы в PDF
// Манипуляции со стилями
$p = str_replace ('</head>', '<style>body { zoom: 2; } * {color: #000 !important; } .fixed-buttons {position: fixed; right: 0; bottom: 0; margin-top: 0; background-color:gray; width:40px; height:40px;} </style></head>', $p); 
// Код кнопки (перед закрывающим тегом BODY)
$p = str_replace ('</body>', '<a class="fixed-buttons" href="http://selectpdf.com/save-as-pdf">PDF</a></body>', $p);


Да, это решение работало. Selectpdf.com по значению поля referer определяет, откуда пришел юзер, конвертирует эту страницу и отдает с нужным mime-заголовком. Для юзера это означает ровно то, что он вообще не подозревает о существовании selectpdf.com, а лишь жмакает на волшебную кнопку на моем сайте — и практически моментально скачивается pdf. Да, кстати, «моего сайта» — не совсем корректное выражение… Ведь в данном случае весь контент любезно предоставлен Хабром. Однако я не планирую выкладывать сервис в публичный доступ и думаю, что для моих личных целей такой парсинг CURL-ом не приносит никаких проблем самому Хабру в данном случае.

Итак, мы вроде бы близки к своей цели — прочесть страничку в волшебном скачанном pdf-варианте. Но Kindle снова заготовил нам сюрприз! Скачивание в браузере разрешено только в нескольких форматах — MOBI, TXT, и еще парочке. Не совсем понятное ограничение, ведь сама книга вполне справляется с большинством форматов, включая даже RTF и Word-овский DOC(X).

Ну что ж, тут включается ненормальное программирование. И азарт.

Я вспоминаю про одну любопытную возможность синхронизации документов, которую заложил Amazon в своем девайсе. А именно — у каждого авторизованного юзера есть личный кабинет и личный «киндловский» емайл, на который можно с белого списка ящиков посылать письма с документами и они волшебным образом появятся в книге. Некая пародия на dropbox и другие облачные системы хранения файлов. Но не использовать такую возможность было бы глупо. К тому же, проведенные тесты показали, что в таком сценарии «курьерской доставки документов на книгу» успешно загружается хоть PDF, хоть любой другой из списка поддерживаемых Киндлом.

Теперь о второй половинке «мостика», который мы будем дальше строить. И который именно как мостик срастался в моей голове во время анализа всех вариантов решения поставленной задачи. А именно — сервис PdfByEmail, предоставляемый одним из тех сервисов, которые я мониторил ранее. Суть — можно отправить целевой url порталу web2pdfconvert.com в письме, указав в теме письма ключевое слово Convert и в ответ (как обещают разработчики сервиса — в течении 1...3 мин) придет письмо с приаттаченным PDF-ником. Гениально! Для нашего случая самое то. Ведь мы можем сделать так, чтобы ответное письмо отсылалось прямо на книгу, т.е. на ее авторизованный email. Все, что нужно — это добавить email сервиса конвертации no-reply@web2pdfconvert.com в белый список личного кабинета Amazon.

Мне пришлось переписать функцию все той же fixed-кнопки внизу сайта. Теперь она запускает ajax-запрос к другой странице моего сайта, указывая, какой url требуется парсить. Т.к. не было уверенности, подключен ли jquery на всех сайтах, где я буду бродить, а подключать свой было бы громоздко, пришлось вспомнить давние времена, когда я делал ajax на нативном js, без фреймворков.



Связка js и php, отвечающая за отправку письма в сервис конвертации
Фронтэнд
// AJAX
function getXmlHttp() {
  var xmlhttp;
  try {xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");}
  catch (e) {
    try {xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}
	catch (E) {xmlhttp = false;}
  }
  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
    xmlhttp = new XMLHttpRequest();
  }
  return xmlhttp;
}

// ajax-отправка команды
function www_to_pdf(q) {
	var req = getXmlHttp();
	req.onreadystatechange = function() {  
		if (req.readyState == 4) { 
			if(req.status == 200) { 
				if (req.responseText == "ok") {
					alert('Запрос на конвертацию документа успешно отправлен!');
				} else alert(req.responseText);
			} else alert(req.status);
		}
	}
	req.open('GET', 'www_to_pdf.php?q=' + encodeURIComponent(q), true);
	req.send(null);  // отослать запрос
}

Бэкенд:

$q = $_GET['q'];
if ( get_magic_quotes_gpc()) $q=addslashes(stripslashes(trim($q)));

// Отправляем e-mail
$title = "convert"; 
$headers = "From: myamazonlogin<myamazonlogin@kindle.com>\r\nReply-To: myamazonlogin<myamazonlogin@kindle.com>\r\nContent-type: text/plain; charset=utf-8\r\n";
$ret = mail('submit@web2pdfconvert.com', $title, "http://mysite.ru/proxy.php?exp=1&q=$q", $headers);
if ($ret == 1) echo 'ok';
else echo $ret;

Параметр exp=1 в отправляемой ссылке я добавил для чуть разного отображения веб-страницы для конвертора PDF и для моего просмотра.

Конвертор слишком «мельчил», и для него я делал тройной масштаб body в css. Кроме того, в экспортном документе не должно было быть видно моей fixed-кнопки. Этим и заправлял флаг exp=1.

Конечно, вначале я протестировал отправку на свой ящик, а уже потом решился запустить всю цепочку. Больше всего я боялся, что мое письмо, отправленное с домена, не совпадающего с доменом электронной почты для ответа kindle.com, будет забанено спам-фильтрами PDF-сервиса. Но все прошло успешно и заработало с первого раза. Ровно 2-3 минуты, как и утверждали разработчики web2pdfconvert — и файл валится в книгу с шильдиком NEW.

Подведу итоги. Теперь, благодаря самодельному прокси, я имею возможность серфить любимые сайты в книге в удобном масштабе, контрастности. А наткнувшись на интересную статью, одним кликом отправить ее на конвертацию, зная, что через три минуты она свалится мне в книгу в PDF-варианте. Грубо говоря, можно ежедневно отсматривать новостную ленту и накликать статьи, которые тебя заинтересовали. А потом уже читать их оффлайн и с удобным юзабилити. Кстати, как оказалось, web2pdfconvert сохраняет ссылки. Так что даже читая PDF я могу так же, как и раньше, открыть что-то по гиперссылке из документа. К примеру, раздел комментарии или «похожие статьи» внизу.

Кстати, мне пришлось внедрить еще небольшой хак касательно спойлеров в статьях Хабра. Ведь в PDF их уже не откроешь…

Проблема решилась еще парочкой корректирующих вставок
$p = str_replace ('</head>', '<style>body { zoom: ' . ( $exp ? 3 : 2 ) . '; } * {color: #000 !important; } .fixed-buttons {position: fixed; right: 0; bottom: 0; margin-top: 0; background-color:gray; width:40px; height:40px;} .spoiler_text {display:block;} </style><script type="text/javascript" src="http://mysite.ru/scripts/js/www_to_pdf.js"></script></head>', $p); 
$p = str_replace ('class="spoiler_text"', 'class="spoiler_text" style="display:block; line-height: 120%;"', $p);

Обратите внимание на вышеупомянутый параметр $exp, который варьирует масштаб для конвертации и простого просмотра.

Финита ля комедия! Теперь чтение любимых статей, да еще и через самодельный костыль (ну и что, что костыль) — приносит еще большее наслаждение. Конечно, есть еще куда совершенствовать идею. К примеру, имена файлов, которые присылает конвертатор, равны mysyte-ru и не зависят от дальнейшей ссылки. Думаю, что можно решить через создание поддоменов. Чтобы хотя бы supernovost-mysyte-ru или еще как-то различались названием. Надо почитать про ограничения длины доменных имен. Ну и в заголовках еще иногда подглючивает конвертатор (см.ниже). Но это мелочи.

Поделиться публикацией
Комментарии 27
    +3
    Тут уже проскакивали экспортилки в PDF (1, 2). Но у вас, я так понимаю, это некий гейт, который делает читабельную версию «на лету».
      +2
      Задумка была именно в том, чтобы с утра за кофе из дома по wifi полазить по хабру, выловить интересные посты с хабра, «отправить» их на конвертацию. Пока одеваешься и собираешься, емайл-транзакции проходят, и книги уже в гаджете. И потом весь день можно в оффлайн их читать ;)
        0
        Не раз думал о том же, отличное решение.
          +1
          По-моему, именно это и делает Pocket. И полне успешно, и без всяких лишних транзакций.
            0
            Да, но в Pocket с книги не зайдешь…
            Штука безусловно интересная, благодарю за отзыв.
            Я протестировал ее. Результат ниже. Увы, все не так радужно.
            image
              0
              Проверил на компе. Действительно удаляет спойлеры. Эх, нет в жизни счастья. :)
                0
                В принципе, при должном усовершенствовании и внедрении минимальной защиты от ботов, могу выложить в публичный доступ. Буду рад услышать рекомендации в этом направлении. Может быть, и «экспортилки» есть более продвинутые (больше всего напрягает имя файла, текущая версия именует все файлы mysite-ru.pdf, согласно имени домена моего «прокси-гейта», создавать поддомены можно — но как-то костыльно… ) для себе еще норм, но если в публичном доступе будет, надо какое-то более разумное решение искать.
                Ну и опять же важно понять ЦА, нужна ли людям подобная фича. По своему опыту могу сказать, что реально удобно. Частенько гоняю в деревню, в баньку. Но мозг там без высоких технологий и связи скучает. Заранее накачал интересных статей с хабра, и потом сидишь в оффлайне изучаешь.
                  0
                  До тех пор, пока у статей не будет версии для печати, спойлер — зло. Он пропадает и при «печати» на PDF-принтере и при [CTRL+A, CTRL+C, CTRL+V].
              +1
              у kindle это штатно организованно в виде плагина для браузера, который по нажатию кнопки конвертирует страничку в удобоваримый формат для читалки и отправляет ее по wifi.
                0
                а можно поподробнее о плагине для браузера?
                0
                «для браузера компьютера» :) Моя статья о том, как достичь полной автономии данного действия, используя только сам гаджет и его более чем примитивный браузер. Это более актуально, к примеру, в поездках. Кроме того, я решил ещё часть задач, которые сам по себе плагин хрома точно не решает: открытие хабра-спойлеров в статьях со скрытым контентом перед экспортом, масштабирование шрифта для более удобного чтения, смена цвета шрифта на чёрный для контрастности…
              +4
              Может быть, нам просто надо попросить сделать мобильную версию такой, какая нам нужна?
                0
                Хорошая идея вообще попросить их заняться поплотнее браузером) все же доля интернето-чтения, я думаю, достаточно высока, чтобы оставлять браузер в таком запущенном состоянии…
                Скрин настроек браузера улыбает ;))) Хотя — для обывателя возможно, все, кроме Javascript, понятно )))
                image
                +1
                C помощью PhantomJS достаточно несложно сделать свой рендер страниц в PDF, без использования стороних сервисов.
                Пример есть тут — github.com/ariya/phantomjs/blob/master/examples/rasterize.js
                  0
                  Вот даже более подробно и понятно phantomjs.org/screen-capture.html
                    0
                    интересно! Спасибо, поизучаю. Сейчас в работе как раз один из проектов, — с экспортом в PDF связанный…
                      0
                      Он на страницы очень безмозгло режет: просто шинкует пост на страницы заданного формата, разрезая не глядя на контент, даже картинки. Еще с заданием размера полей не все просто. Экспериментировал с ним, когда выход второй версии Экспорта избранного в Хабр задерживался.
                      0
                      Извините, из статьи не понял, чем не устроил штатный сервис Send to Kindle с плагинами под Chrome и Firefox?
                        0
                        тем, что для попадания в мозиллу или хром нужен как минимум комп;)
                          0
                          UPD: Протестировал штатный сервис Send to Kindle. Штука определенно хорошая, и я благодарен maximw за отсылку к ней.
                          Но вот конкретно для Хабра не панацея… Спойлеры теряются (см. скрин сравнения ниже). Поэтому возражение пока не принимается ))
                          image
                          0
                          if ( get_magic_quotes_gpc()) $q=addslashes(stripslashes(trim($q)));
                          

                          Не знаю какую версию PHP вы используете, но get_magic_quotes_gpc() начиная с 5.4 всегда возвращает true, поскольку «магические кавычки» убраны из языка.
                            0
                            Логичное замечание, спасибо. Видимо, увлекся универсальностью кода для стабильной работы на разных версиях PHP ))
                            0
                            Для себя решил это pocket + calibre
                            Pocket достаёт информацию, calibre превращает в azw и отправляет на Kindle.
                            Плюс в том, что работает с любым сайтом, а не только с хабром.
                              0
                              Протестирую) спасибо! calibre оказался реально интересной штуковиной.
                              По комменатриям понимаю, что 90% пользователей все же используют стационарный комп для конвертаций )) Моя статья о том, как я попытался сделать это не выходя за рамки «книги» и ее стандартного убогого браузера.
                                0
                                Calibre протестил, очень клевая вещь, и сама отправляет на киндловский емайл. Уже настроил smtp для отправки, все четко ;)
                                Rayslava, а как Вы стыкуете pocket + calibre между собой? Я сохраняю в pocket, но дальше непонятно как скачать, и вообще как автоматизировать процесс?
                                  +1
                                  Да, собственно через RSS пристыковываю. В calibre есть такая штука, как внешние источники, просто экспортировал unread-ленту pocket в RSS и забираю её с помощью calibre. Если скормить пароль и не делать ленту public, то всё, что забрал calibre, будет в pocket отмечаться прочитанным.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое