Para muchos proyectos he buscado algo que me extraiga el contenido de una web, y si de paso me da información útil como su idioma mejor.

Estos proyectos con cosas como:

  • Un bot xmpp que le mandes una url y te devuelva el texto
  • Un recolector de enlaces que luego con ellos forme un rss con contenido completo
  • Un agregador que necesita saber el idioma de las páginas que esta agregando
  • etc

Pues bien, parece que graby es lo que estaba buscando. Ver https://github.com/j0k3r/graby

Mi interés no es usarla en php, así que haré una instalación y un pequeño php para usar como comando externo para otras aplicaciones.

1- Instalo graby

La manera habitual sería usar compose, pero como no estoy interesado en un proyecto php y además en la raspberry el intento de instalar con compose se ha llevado toda la memoria hasta petar... usare php-download.com

Y para obtener automáticamente la última versión usare este script:

#!/bin/bash
LAST=$(curl -sS "https://php-download.com/package/j0k3r/graby" | grep "selected=\"selected\"" | cut -d'"' -f2)
URL="https://php-download.com/downloads/j0k3r/graby/${LAST}/j0k3r_graby_${LAST}_require.zip"
wget -O graby.zip "$URL"
unzip -o -q graby.zip 'vendor/*'
rm graby.zip

y lo ejecuto:

pi@bot ~/wks/my-graby $ ./get-graby.sh 
--2018-09-26 19:06:12--  https://php-download.com/downloads/j0k3r/graby/1.13.6.0/j0k3r_graby_1.13.6.0_require.zip
Resolviendo php-download.com (php-download.com)... 104.18.41.212, 104.18.40.212, 2606:4700:30::6812:28d4, ...
Conectando con php-download.com (php-download.com)[104.18.41.212]:443... conectado.
Petición HTTP enviada, esperando respuesta... 200 OK
Longitud: 37589435 (36M) [application/zip]
Grabando a: “graby.zip”

graby.zip                                  100%[========================================================================================>]  35,85M  1011KB/s   en 41s    

2018-09-26 19:06:55 (906 KB/s) - “graby.zip” guardado [37589435/37589435]
pi@bot ~/wks/my-graby $ ls
get-graby.sh  vendor

1- Creo el comando

#!/usr/bin/env php
<?php
require_once("vendor/autoload.php");

use Graby\Graby;

$article = $argv[1];

$graby = new Graby();
$result = $graby->fetchContent($article);

echo json_encode($result);
?>

Ejemplo de ejecución:

pi@bot ~/wks/my-graby $ ./graby.php https://s-nt-s.github.io/extension_markdown_pelican/
{"status":200,"html":"<p>Seg\u00fan la documentaci\u00f3n de <code>pelican<\/code> la manera de incluir un plugin <code>markdown<\/code>\nes poniendo en <code>pelicanconf.py<\/code> lo siguiente:<\/p><div class=\"highlight\"><pre>MARKDOWN = {\n    'extensions': [ &lt;&lt;aqu\u00ed van las extensiones&gt;&gt; ],\n    'extension_configs': {\n        'markdown.extensions.codehilite': {'css_class': 'highlight'},\n        'markdown.extensions.extra': {},\n        'markdown.extensions.meta': {},\n    },\n    'output_format': 'html5',\n}\n<\/pre><\/div><p>donde la parte relevante es el array <code>extensions<\/code> ya que lo dem\u00e1s solo se pone\npara preservar el valor por defecto de la variable <code>MARKDOWN<\/code><\/p><p>Si estamos incluyendo extensiones de terceros probablemente esto sea lo mejor,\npero si la extensi\u00f3n la estamos haciendo nosotros puede que queramos evitarnos\ntener que escribir este bloque y as\u00ed mantener m\u00e1s limpio nuestro fichero\n<code>pelicanconf.py<\/code> y de este modo solo pensar en <code>plugins<\/code> en general, sin hacer\ndistinci\u00f3n si son <code>plugins de pelican<\/code> o <code>extensiones de markdown<\/code>.<\/p><p>En definitiva, la idea es es crear la extensi\u00f3n dentro de un plugin.\nEsto tambi\u00e9n es \u00fatil cuando queremos configurar la extensi\u00f3n en base a\nvariables de <code>pelicanconf.py<\/code> evitando tener que pasarlas por par\u00e1metro una a una.<\/p><p>Este ser\u00eda un ejemplo para poder usar variables en <code>markdown<\/code><\/p><p>En <code>pelicanconf.py<\/code>:<\/p><div class=\"highlight\"><pre>import os\nsys.path.append('.') #### 1\nfrom plugins import replacements #### 2\nabspath = os.path.abspath(__file__)\ncur_dir = os.path.dirname(abspath)\nREPLACEMENTS_CONFIG = cur_dir+\"\/config\/replacements.yml\" #### 3\nPLUGINS=[replacements] #### 4\nGITHUB_URL = 'https:\/\/github.com\/s-nt-s\/s-nt-s.github.io'\nDEFAULT_PAGINATION = 10\n<\/pre><\/div><ul><li>Con <code>1<\/code> y <code>2<\/code> incluimos el <code>plugin<\/code> que esta en <code>plugins\/replacements.py<\/code><\/li>\n<li>Con <code>3<\/code> definimos la variable que se usara para obtener el fichero de configuraci\u00f3n<\/li>\n<li>Con <code>4<\/code> incluimos el <code>plugin<\/code><\/li>\n<\/ul><p>En <code>plugins\/replacements.py<\/code> tenemos:<\/p><div class=\"highlight\"><pre>from markdown.preprocessors import Preprocessor\nfrom markdown.extensions import Extension\nfrom pelican import signals\nimport yaml\nclass MkReplace(Preprocessor):\n    def __init__(self, delimiter, replacements):\n        self.replacements = replacements\n        self.delimiter = delimiter\n    def run(self, lines):\n        txt = \"\\n\".join(lines)\n        for key, value in self.replacements.items():\n            txt = txt.replace(self.delimiter+key+self.delimiter, value)\n        return txt.split(\"\\n\")\nclass ExReplace(Extension):\n    def __init__(self, delimiter, replacements, **config):\n        self.replacements = replacements\n        self.delimiter = delimiter\n        super(ExReplace, self).__init__(**config)\n    def extendMarkdown(self, md, md_globals):\n        md.preprocessors.add('replacements', MkReplace(self.delimiter, self.replacements), \"&gt;html_block\")\ndef process_settings(pelican_object):\n    config_file = pelican_object.settings['REPLACEMENTS_CONFIG'] #### 1\n    with open(config_file, 'r') as stream:\n        replacements = yaml.load(stream)\n    if 'PELICAN_SETTINGS' in replacements:\n        pSettings = replacements['PELICAN_SETTINGS']\n        del replacements['PELICAN_SETTINGS']\n        if isinstance(pSettings, str):\n            pSettings = pSettings.strip().split()\n        if isinstance(pSettings, list):\n            for s in pSettings:\n                if s not in replacements and s in pelican_object.settings:\n                    replacements[s]=str(pelican_object.settings[s]) #### 2\n    delimiter = replacements.pop('DELIMITER', '::')\n    return delimiter, replacements\ndef replacements_markdown_extension(pelicanobj, delimiter, replacements):\n    \"\"\"Instantiates a customized Markdown extension\"\"\"\n    pelicanobj.settings['MARKDOWN'].setdefault('extensions', []).append(ExReplace(delimiter, replacements))\ndef replacements_init(pelicanobj):\n    \"\"\"Loads settings and instantiates the Python Markdown extension\"\"\"\n    # Process settings\n    delimiter, replacements = process_settings(pelicanobj)\n    # Configure Markdown Extension\n    replacements_markdown_extension(pelicanobj, delimiter, replacements)\ndef register():\n    \"\"\"Plugin registration\"\"\"\n    signals.initialized.connect(replacements_init)\n<\/pre><\/div><ul><li><code>register<\/code> registra el <code>plugin<\/code><\/li>\n<li><code>replacements_init<\/code> usa <code>pelicanobj<\/code> para preparar las opciones a trav\u00e9s de <code>process_settings<\/code> y pasarselas a <code>replacements_markdown_extension<\/code><\/li>\n<li><code>replacements_markdown_extension<\/code> include la extensi\u00f3n de <code>markdown<\/code> en el <code>pelican<\/code> sin machacar los valores por defecto de la variable <code>MARKDOWN<\/code><\/li>\n<li>El resto es el contenido habitual de una extensi\u00f3n <code>markdown<\/code><\/li>\n<\/ul><p>En <code>process_settings<\/code> vemos como nos es de ayuda tener el objeto <code>pelican<\/code> disponible,\nde manera que tenemos disponible <code>REPLACEMENTS_CONFIG<\/code> para encontrar el <code>yaml<\/code> de configuraci\u00f3n (en <code>1<\/code>)\ny a su vez podemos usar otras variables (en <code>2<\/code>) de manera que si el <code>yaml<\/code> es:<\/p><div class=\"highlight\"><pre>DELIMITER: '::'\nPELICAN_SETTINGS: GITHUB_URL DEFAULT_PAGINATION\nLOREM: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...\n<\/pre><\/div><p>y tenemos un <code>markdown<\/code> tal que as\u00ed:<\/p><div class=\"highlight\"><pre>Este blog usa una imaginaci\u00f3n de **::DEFAULT_PAGINATION::** items por hoja y\npuedes encontrar el c\u00f3digo en [github](::GITHUB_URL::)\n::LOREN::\n<\/pre><\/div><p>se transformara justo antes de ser pasado a <code>html<\/code> en:<\/p><div class=\"highlight\"><pre>Este blog usa una imaginaci\u00f3n de **10** items por hoja y\npuedes encontrar el c\u00f3digo en [github](https:\/\/github.com\/s-nt-s\/s-nt-s.github.io)\nNeque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...\n<\/pre><\/div><p>y finalmente en el <code>html<\/code><\/p><div class=\"highlight\"><pre>&lt;p&gt;Este blog usa una imaginaci\u00f3n de &lt;b&gt;10&lt;\/b&gt; items por hoja y\npuedes encontrar el c\u00f3digo en &lt;a href='https:\/\/github.com\/s-nt-s\/s-nt-s.github.io'&gt;github&lt;\/a&gt;&lt;\/p&gt;\n&lt;p&gt;Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...&lt;\/p&gt;\n<\/pre><\/div><p>Resumo las ventajas:<\/p><ul><li>menos lineas en <code>pelicanconf.py<\/code><\/li>\n<li>no hay peligro de machacar valores por defecto de <code>MARKDOWN<\/code> necesarios<\/li>\n<li>la extensi\u00f3n tiene acceso al objeto <code>pelican<\/code> y por lo tanto a sus variables de configuraci\u00f3n<\/li>\n<li>la extensi\u00f3n es m\u00e1s f\u00e1cil de configurar<\/li>\n<li>no hace falta diferencia entre <code>extensiones markdown<\/code> y <code>plugins pelican<\/code><\/li>\n<\/ul><p>Fuentes:\n<a href=\"http:\/\/docs.getpelican.com\/en\/stable\/settings.html#settings\" target=\"_blank\">docs.getpelican.com - settings<\/a>,\n<a href=\"http:\/\/docs.getpelican.com\/en\/stable\/plugins.html#how-to-create-plugins\" target=\"_blank\">docs.getpelican.com - plugins<\/a>,\n<a href=\"https:\/\/github.com\/s-nt-s\/s-nt-s.github.io\" target=\"_blank\">mi github :P<\/a><\/p>","title":"Crear extensi\u00f3n markdown para usar en pelican","language":"es","date":null,"authors":[],"url":"https:\/\/s-nt-s.github.io\/extension_markdown_pelican\/","content_type":"text\/html","open_graph":[],"native_ad":false,"all_headers":{"server":"GitHub.com","content-type":"text\/html; charset=utf-8","last-modified":"Wed, 26 Sep 2018 08:59:31 GMT","etag":"W\/\"5bab4a73-538d\"","access-control-allow-origin":"*","expires":"Wed, 26 Sep 2018 17:01:49 GMT","cache-control":"max-age=600","content-encoding":"gzip","x-github-request-id":"4272:358F:3836627:4B61346:5BABB923","content-length":"4138","accept-ranges":"bytes","date":"Wed, 26 Sep 2018 17:13:51 GMT","via":"1.1 varnish","age":"0","connection":"keep-alive","x-served-by":"cache-mad9442-MAD","x-cache":"HIT","x-cache-hits":"1","x-timer":"S1537982031.486043,VS0,VE110","vary":"Accept-Encoding","x-fastly-request-id":"15e8bf8ca5b6dd90d6f63a1d0d5c5b8c50e349d7"},"summary":"Seg\u00fan la documentaci\u00f3n de pelican la manera de incluir un plugin markdown es poniendo en pelicanconf.py lo siguiente: MARKDOWN = { 'extensions': [ &lt;&lt;aqu\u00ed van las extensiones&gt;&gt; ], 'extension_configs': { 'markdown.extensions.codehilite': {'css_class': &hellip;"}

es decir, devuelve el json:

{
    "all_headers": {
        "accept-ranges": "bytes",
        "access-control-allow-origin": "*",
        "age": "0",
        "cache-control": "max-age=600",
        "connection": "keep-alive",
        "content-encoding": "gzip",
        "content-length": "4138",
        "content-type": "text/html; charset=utf-8",
        "date": "Wed, 26 Sep 2018 17:27:48 GMT",
        "etag": "W/\"5bab4a73-538d\"",
        "expires": "Wed, 26 Sep 2018 17:01:49 GMT",
        "last-modified": "Wed, 26 Sep 2018 08:59:31 GMT",
        "server": "GitHub.com",
        "vary": "Accept-Encoding",
        "via": "1.1 varnish",
        "x-cache": "HIT",
        "x-cache-hits": "1",
        "x-fastly-request-id": "e3088257cb9a3cd8805bc44ee055908f3c01be54",
        "x-github-request-id": "4272:358F:3836627:4B61346:5BABB923",
        "x-served-by": "cache-mad9442-MAD",
        "x-timer": "S1537982868.240513,VS0,VE107"
    },
    "authors": [],
    "content_type": "text/html",
    "date": null,
    "html": "<p>Seg\u00fan la documentaci\u00f3n de <code>pelican</code> la manera de incluir un plugin <code>markdown</code>\nes poniendo en <code>pelicanconf.py</code> lo siguiente:</p><div class=\"highlight\"><pre>MARKDOWN = {\n    'extensions': [ &lt;&lt;aqu\u00ed van las extensiones&gt;&gt; ],\n    'extension_configs': {\n        'markdown.extensions.codehilite': {'css_class': 'highlight'},\n        'markdown.extensions.extra': {},\n        'markdown.extensions.meta': {},\n    },\n    'output_format': 'html5',\n}\n</pre></div><p>donde la parte relevante es el array <code>extensions</code> ya que lo dem\u00e1s solo se pone\npara preservar el valor por defecto de la variable <code>MARKDOWN</code></p><p>Si estamos incluyendo extensiones de terceros probablemente esto sea lo mejor,\npero si la extensi\u00f3n la estamos haciendo nosotros puede que queramos evitarnos\ntener que escribir este bloque y as\u00ed mantener m\u00e1s limpio nuestro fichero\n<code>pelicanconf.py</code> y de este modo solo pensar en <code>plugins</code> en general, sin hacer\ndistinci\u00f3n si son <code>plugins de pelican</code> o <code>extensiones de markdown</code>.</p><p>En definitiva, la idea es es crear la extensi\u00f3n dentro de un plugin.\nEsto tambi\u00e9n es \u00fatil cuando queremos configurar la extensi\u00f3n en base a\nvariables de <code>pelicanconf.py</code> evitando tener que pasarlas por par\u00e1metro una a una.</p><p>Este ser\u00eda un ejemplo para poder usar variables en <code>markdown</code></p><p>En <code>pelicanconf.py</code>:</p><div class=\"highlight\"><pre>import os\nsys.path.append('.') #### 1\nfrom plugins import replacements #### 2\nabspath = os.path.abspath(__file__)\ncur_dir = os.path.dirname(abspath)\nREPLACEMENTS_CONFIG = cur_dir+\"/config/replacements.yml\" #### 3\nPLUGINS=[replacements] #### 4\nGITHUB_URL = 'https://github.com/s-nt-s/s-nt-s.github.io'\nDEFAULT_PAGINATION = 10\n</pre></div><ul><li>Con <code>1</code> y <code>2</code> incluimos el <code>plugin</code> que esta en <code>plugins/replacements.py</code></li>\n<li>Con <code>3</code> definimos la variable que se usara para obtener el fichero de configuraci\u00f3n</li>\n<li>Con <code>4</code> incluimos el <code>plugin</code></li>\n</ul><p>En <code>plugins/replacements.py</code> tenemos:</p><div class=\"highlight\"><pre>from markdown.preprocessors import Preprocessor\nfrom markdown.extensions import Extension\nfrom pelican import signals\nimport yaml\nclass MkReplace(Preprocessor):\n    def __init__(self, delimiter, replacements):\n        self.replacements = replacements\n        self.delimiter = delimiter\n    def run(self, lines):\n        txt = \"\\n\".join(lines)\n        for key, value in self.replacements.items():\n            txt = txt.replace(self.delimiter+key+self.delimiter, value)\n        return txt.split(\"\\n\")\nclass ExReplace(Extension):\n    def __init__(self, delimiter, replacements, **config):\n        self.replacements = replacements\n        self.delimiter = delimiter\n        super(ExReplace, self).__init__(**config)\n    def extendMarkdown(self, md, md_globals):\n        md.preprocessors.add('replacements', MkReplace(self.delimiter, self.replacements), \"&gt;html_block\")\ndef process_settings(pelican_object):\n    config_file = pelican_object.settings['REPLACEMENTS_CONFIG'] #### 1\n    with open(config_file, 'r') as stream:\n        replacements = yaml.load(stream)\n    if 'PELICAN_SETTINGS' in replacements:\n        pSettings = replacements['PELICAN_SETTINGS']\n        del replacements['PELICAN_SETTINGS']\n        if isinstance(pSettings, str):\n            pSettings = pSettings.strip().split()\n        if isinstance(pSettings, list):\n            for s in pSettings:\n                if s not in replacements and s in pelican_object.settings:\n                    replacements[s]=str(pelican_object.settings[s]) #### 2\n    delimiter = replacements.pop('DELIMITER', '::')\n    return delimiter, replacements\ndef replacements_markdown_extension(pelicanobj, delimiter, replacements):\n    \"\"\"Instantiates a customized Markdown extension\"\"\"\n    pelicanobj.settings['MARKDOWN'].setdefault('extensions', []).append(ExReplace(delimiter, replacements))\ndef replacements_init(pelicanobj):\n    \"\"\"Loads settings and instantiates the Python Markdown extension\"\"\"\n    # Process settings\n    delimiter, replacements = process_settings(pelicanobj)\n    # Configure Markdown Extension\n    replacements_markdown_extension(pelicanobj, delimiter, replacements)\ndef register():\n    \"\"\"Plugin registration\"\"\"\n    signals.initialized.connect(replacements_init)\n</pre></div><ul><li><code>register</code> registra el <code>plugin</code></li>\n<li><code>replacements_init</code> usa <code>pelicanobj</code> para preparar las opciones a trav\u00e9s de <code>process_settings</code> y pasarselas a <code>replacements_markdown_extension</code></li>\n<li><code>replacements_markdown_extension</code> include la extensi\u00f3n de <code>markdown</code> en el <code>pelican</code> sin machacar los valores por defecto de la variable <code>MARKDOWN</code></li>\n<li>El resto es el contenido habitual de una extensi\u00f3n <code>markdown</code></li>\n</ul><p>En <code>process_settings</code> vemos como nos es de ayuda tener el objeto <code>pelican</code> disponible,\nde manera que tenemos disponible <code>REPLACEMENTS_CONFIG</code> para encontrar el <code>yaml</code> de configuraci\u00f3n (en <code>1</code>)\ny a su vez podemos usar otras variables (en <code>2</code>) de manera que si el <code>yaml</code> es:</p><div class=\"highlight\"><pre>DELIMITER: '::'\nPELICAN_SETTINGS: GITHUB_URL DEFAULT_PAGINATION\nLOREM: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...\n</pre></div><p>y tenemos un <code>markdown</code> tal que as\u00ed:</p><div class=\"highlight\"><pre>Este blog usa una imaginaci\u00f3n de **::DEFAULT_PAGINATION::** items por hoja y\npuedes encontrar el c\u00f3digo en [github](::GITHUB_URL::)\n::LOREN::\n</pre></div><p>se transformara justo antes de ser pasado a <code>html</code> en:</p><div class=\"highlight\"><pre>Este blog usa una imaginaci\u00f3n de **10** items por hoja y\npuedes encontrar el c\u00f3digo en [github](https://github.com/s-nt-s/s-nt-s.github.io)\nNeque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...\n</pre></div><p>y finalmente en el <code>html</code></p><div class=\"highlight\"><pre>&lt;p&gt;Este blog usa una imaginaci\u00f3n de &lt;b&gt;10&lt;/b&gt; items por hoja y\npuedes encontrar el c\u00f3digo en &lt;a href='https://github.com/s-nt-s/s-nt-s.github.io'&gt;github&lt;/a&gt;&lt;/p&gt;\n&lt;p&gt;Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...&lt;/p&gt;\n</pre></div><p>Resumo las ventajas:</p><ul><li>menos lineas en <code>pelicanconf.py</code></li>\n<li>no hay peligro de machacar valores por defecto de <code>MARKDOWN</code> necesarios</li>\n<li>la extensi\u00f3n tiene acceso al objeto <code>pelican</code> y por lo tanto a sus variables de configuraci\u00f3n</li>\n<li>la extensi\u00f3n es m\u00e1s f\u00e1cil de configurar</li>\n<li>no hace falta diferencia entre <code>extensiones markdown</code> y <code>plugins pelican</code></li>\n</ul><p>Fuentes:\n<a href=\"http://docs.getpelican.com/en/stable/settings.html#settings\" target=\"_blank\">docs.getpelican.com - settings</a>,\n<a href=\"http://docs.getpelican.com/en/stable/plugins.html#how-to-create-plugins\" target=\"_blank\">docs.getpelican.com - plugins</a>,\n<a href=\"https://github.com/s-nt-s/s-nt-s.github.io\" target=\"_blank\">mi github :P</a></p>",
    "language": "es",
    "native_ad": false,
    "open_graph": [],
    "status": 200,
    "summary": "Seg\u00fan la documentaci\u00f3n de pelican la manera de incluir un plugin markdown es poniendo en pelicanconf.py lo siguiente: MARKDOWN = { 'extensions': [ &lt;&lt;aqu\u00ed van las extensiones&gt;&gt; ], 'extension_configs': { 'markdown.extensions.codehilite': {'css_class': &hellip;",
    "title": "Crear extensi\u00f3n markdown para usar en pelican",
    "url": "https://s-nt-s.github.io/extension_markdown_pelican/"
}

3- Lo uso desde python

#!/usr/bin/python3
import json
import sys
from subprocess import check_output, STDOUT

url = sys.argv[1]
js = check_output(['./graby.php', url], stderr=STDOUT).decode('UTF-8')
js = json.loads(js)
js = json.dumps(js, indent=4, sort_keys=True)
print(js)

Fuentes: github.com - j0k3r/graby, php-download.com - j0k3r/graby