Categorías
Python

La implementación de una aplicación de Django a AWS Elastic Beanstalk

 

Tabla de Contenidos

  • Instalación Requisitos
  • Refactor el Índice Ruta
  • Pide suceso de
  • ProcessingWhat texto? pasando de
  • ¿Qué? acontecimiento
  • Mostrar resultados
  • Resumen
  • de qué?

En esta parte de la serie, vamos a raspar el contenido de una página web y luego procesar el texto para el conteo de palabras pantalla.

Actualizaciones :

  • 22/03/2016: actualizado a la versión 3.5.1 de Python, así como las últimas versiones de las solicitudes, BeautifulSoup y NLTK. Véase más abajo para más detalles.
  • 02/22/2015: Agregado Python 3 apoyo. Bono

gratuito: Haga clic aquí para obtener acceso a un tutorial de vídeo libres Frasco + Python que muestra cómo construir aplicación web Frasco, paso a paso.

Recuerde: Esto es lo que estamos construyendo – Un Frasco aplicación que calcula pares de palabras de frecuencia basado en el texto de una determinada URL.

necesita el código? Agarrarlo desde el repositorio.

Instalación Requisitos

herramientas utilizadas: las solicitudes

  • (2.9.1) – una biblioteca para el envío de peticiones HTTP
  • BeautifulSoup (4.4.1) – una herramienta que se utiliza para raspar y procesadores de documentos desde la web
  • Natural Language Toolkit (3.2) – una biblioteca de procesamiento de lenguaje natural

Navegar en el directorio del proyecto para activar el entorno virtual, a través de autoenv, y luego instalar los requisitos:

$ cd flask-by-example
$ pip install requests==2.9.1 beautifulsoup4==4.4.1 nltk==3.2
$ pip freeze > requirements.txt

Refactor el Índice Ruta

para empezar, vamos a deshacernos de la parte “hola mundo” de la ruta de índice en nuestro archivo app.py y establecer la ruta para hacer una forma de aceptar direcciones URL. En primer lugar, añadir una carpeta de plantillas para mantener nuestras plantillas y añadir un archivo index.html a ella.

$ mkdir templates
$ touch templates/index.html

configure una página HTML muy básico:




Wordcount<itle><br /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="/<br />etdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" media="screen"></p> <style> .container { max-width: 1000px; } </style> <p> </head><br /> <body></p> <div class="container"> <h1>Wordcount 3000</h1> <form role="form" method='POST' action='/'> <div class="form-group"> <input type="text" name="url" class="form-control" id="url-box" placeholder="Enter URL..." style="max-width: 300px;" autofocus required> </div> <p> <button type="submit" class="btn btn-default">Submit</button><br /> </form> <p> {% for error in errors %}</p> <h4>{{ error }}</h4> <p> {% endfor %} </p></div> <p> <script src="//code.jquery.com/jquery-2.2.1.min.js"></script><br /> <script src="/<br />etdna.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script><br /> </body><br /> </html><br /> </code> </p> <p> Se utilizó Bootstrap para añadir un poco de estilo por lo que nuestra página no es completamente horrible. Luego añadimos un formulario con un cuadro de entrada de texto para los usuarios introducir una URL en. Además, se utilizó un Jinja bucle para iterar a través de una lista de errores, mostrando cada uno. </p> <p> actualización <em> app.py </em> para servir a la plantilla: </p> <p> <code>import os<br /> from flask import Flask, render_template<br /> from flask.ext.sqlalchemy import SQLAlchemy</p> <p>app = Flask(__name__)<br /> app.config.from_object(os.environ['APP_SETTINGS'])<br /> app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False<br /> db = SQLAlchemy(app)</p> <p>from models import Result</p> <p>@app.route('/', methods=['GET', 'POST'])<br /> def index():<br /> return render_template('index.html')</p> <p>if __name__ == '__main__':<br /> app.run()<br /> </code> </p> <p> Por tanto HTTP métodos, métodos = [ ‘GET’, ‘post? Bueno, eventualmente utilizar esa misma ruta tanto para peticiones GET y POST de – para servir a la página del mango y formar presentaciones <em> index.html </em>, respectivamente. </p> <p> fuego de la aplicación para probarlo: </p> <p> <code>$ python manage.py runserver<br /> </code> </p> <p> Vaya a http: // localhost: 5000 / y debería ver la forma que te observa. </p> <h2> Pide </h2> <p> Ahora vamos a utilizar la biblioteca de solicitudes para agarrar la página HTML de la URL presentada. </p> <p> Cambiar ruta su índice de este modo: </p> <p> <code>@app.route('/', methods=['GET', 'POST'])<br /> def index():<br /> errors = []<br /> results = {}<br /> if request.method == "POST":<br /> # get url that the user has entered<br /> try:<br /> url = request.form['url']<br /> r = requests.get(url)<br /> print(r.text)<br /> except:<br /> errors.append(<br /> "Unable to get URL. Please make sure it's valid and try again."<br /> )<br /> return render_template('index.html', errors=errors, results=results)<br /> </code> </p> <p> Asegúrese de actualizar las importaciones, así: </p> <p> <code>import os<br /> import requests<br /> from flask import Flask, render_template, request<br /> from flask.ext.sqlalchemy import SQLAlchemy<br /> </code> </p> <p> Dentro de la vista en sí, que comprueba si la solicitud es un GET o POST-</p> <ul> <li> Si la POST: Tomamos el valor (URL) de la forma y asignado a la variable url. A continuación, añadimos una excepción para manejar los errores y, si es necesario, adjuntó una mensaje de error genérico a la lista de errores. Por último, prestamos la plantilla, incluyendo el diccionario lista de errores y resultados. </li> <li> Si GET: Simplemente dictado la plantilla. La prueba de </li> </ul> <p> <strong> Let esto: </strong> </p> <p> <code>$ python manage.py runserver<br /> </code> </p> <p> Usted debe ser capaz de escribir en una página web válido y en el terminal que va a ver el texto de la página devuelta. </p> <h2> de procesamiento de textos </h2> <p> Con el código HTML en la mano, ahora vamos a contar la frecuencia de las palabras que están en la página y mostrarlos al usuario final. Actualizar el código en <em> app.py </em> a la siguiente y vamos a caminar a través de lo que está sucediendo: </p> <p> <code>import os<br /> import requests<br /> import operator<br /> import re<br /> import nltk<br /> from flask import Flask, render_template, request<br /> from flask.ext.sqlalchemy import SQLAlchemy<br /> from stop_words import stops<br /> from collections import Counter<br /> from bs4 import BeautifulSoup</p> <p>app = Flask(__name__)<br /> app.config.from_object(os.environ['APP_SETTINGS'])<br /> app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True<br /> db = SQLAlchemy(app)</p> <p>from models import Result</p> <p>@app.route('/', methods=['GET', 'POST'])<br /> def index():<br /> errors = []<br /> results = {}<br /> if request.method == "POST":<br /> # get url that the person has entered<br /> try:<br /> url = request.form['url']<br /> r = requests.get(url)<br /> except:<br /> errors.append(<br /> "Unable to get URL. Please make sure it's valid and try again."<br /> )<br /> return render_template('index.html', errors=errors)<br /> if r:<br /> # text processing<br /> raw = BeautifulSoup(r.text, 'html.parser').get_text()<br /> nltk.data.path.append('.<br />ltk_data/') # set the path<br /> tokens = nltk.word_tokenize(raw)<br /> text = nltk.Text(tokens)<br /> # remove punctuation, count raw words<br /> nonPunct = re.compile('.*[A-Za-z].*')<br /> raw_words = [w for w in text if nonPunct.match(w)]<br /> raw_word_count = Counter(raw_words)<br /> # stop words<br /> no_stop_words = [w for w in raw_words if w.lower() not in stops]<br /> no_stop_words_count = Counter(no_stop_words)<br /> # save the results<br /> results = sorted(<br /> no_stop_words_count.items(),<br /> key=operator.itemgetter(1),<br /> reverse=True<br /> )<br /> try:<br /> result = Result(<br /> url=url,<br /> result_all=raw_word_count,<br /> result_no_stop_words=no_stop_words_count<br /> )<br /> db.session.add(result)<br /> db.session.commit()<br /> except:<br /> errors.append("Unable to add item to database.")<br /> return render_template('index.html', errors=errors, results=results)</p> <p>if __name__ == '__main__':<br /> app.run()<br /> </code> </p> <p> Crear un nuevo archivo llamado <em> stop_words.py </em> y añadir la siguiente lista: suceso de </p> <p> <code>stops = [<br /> 'i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you',<br /> 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his',<br /> 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself',<br /> 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which',<br /> 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are',<br /> 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having',<br /> 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if',<br /> 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for',<br /> 'with', 'about', 'against', 'between', 'into', 'through', 'during',<br /> 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in',<br /> 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then',<br /> 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any',<br /> 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no',<br /> 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's',<br /> 't', 'can', 'will', 'just', 'don', 'should', 'now', 'id', 'var',<br /> 'function', 'js', 'd', 'script', '\'script', 'fjs', 'document', 'r',<br /> 'b', 'g', 'e', '\'s', 'c', 'f', 'h', 'l', 'k'<br /> ]<br /> </code> </p> <h3> ¿Qué? </h3> <p> <strong> de procesamiento de textos </strong> </p> <p> En nuestra ruta índice que utiliza BeautifulSoup para limpiar el texto, mediante la eliminación de las etiquetas HTML, que regresamos de la URL, así como NLTK a- </p> <ul> <li> Tokenize el texto en bruto (romper la texto en palabras individuales), y </li> <li> a su vez las fichas en un objeto de texto NLTK. </li> </ul> <p> Para que NLTK funcione correctamente, es necesario descargar los tokenizers correctas. En primer lugar, crear un nuevo directorio – mkdir nltk_data – a continuación, ejecute – python -m nltk.downloader. </p> <p> Cuando aparece la ventana de instalación, actualizar el ‘directorio de descarga’ a <em> whatever_the_absolute_path_to_your_app_is / nltk_data / </em>. </p> <p> A continuación, haga clic en la pestaña ‘modelos’ y seleccione ‘Punkt’ en la columna ‘identificador’. Haga clic en ‘Descargar’. Consulte la documentación oficial para más información. </p> <p> <strong> Eliminar Puntuacion, contar las palabras crudas palabras </strong> </p> <p> <strong> parada </strong> </p> <p> Nuestra producción actual contiene una gran cantidad de palabras que probablemente no queremos contar – es decir, “yo”, “yo”, “la”, Etcétera. Éstos se llaman las palabras vacías. </p> <p> Para una más robusta lista de palabras de parada, utilice el NLTK palabras vacías corpus. </p> <p> <strong> guardar los resultados </strong> </p> <p> Por último, se utilizó un try / excepto para salvar los resultados de la búsqueda y los posteriores recuentos a la base de datos. </p> <h2> Resultados de Pantalla de actualización de </h2> <p> Let <em> index.html </em> con el fin de mostrar los resultados: </p> <p> <code><!DOCTYPE html><br /> <html><br /> <head><br /> <title>Wordcount<itle><br /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="/<br />etdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" media="screen"></p> <style> .container { max-width: 1000px; } </style> <p> </head><br /> <body></p> <div class="container"> <div class="row"> <div class="col-sm-5 col-sm-offset-1"> <h1>Wordcount 3000</h1> <p></p> <form role="form" method="POST" action="/"> <div class="form-group"> <input type="text" name="url" class="form-control" id="url-box" placeholder="Enter URL..." style="max-width: 300px;"> </div> <p> <button type="submit" class="btn btn-default">Submit</button><br /> </form> <p> {% for error in errors %}</p> <h4>{{ error }}</h4> <p> {% endfor %}<br /> </div> <div class="col-sm-5 col-sm-offset-1"> {% if results %}</p> <h2>Frequencies</h2> <p></p> <div id="results"> <table class="table table-striped" style="max-width: 300px;"> <thead> <tr> <th>Word<h></p> <th>Count<h><br /> <r><br /> <head><br /> {% for result in results%}</p> <tr> <td>{{ result[0] }}<d></p> <td>{{ result[1] }}<d><br /> <r><br /> {% endfor %}<br /> <able> </div> <p> {% endif %} </p></div> </p></div> </p></div> <p> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script><br /> <script src="/<br />etdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script><br /> </body><br /> </html><br /> </code> </p> <p> Aquí, hemos añadido una sentencia if para ver si nuestros resultados diccionario tiene nada en ella y luego añadió un bucle for para iterar sobre la resultados y mostrarlos en una tabla. Ejecutar su aplicación y usted debería ser capaz de introducir una URL y recuperar el recuento de las palabras en la página. </p> <p> <code>$ python manage.py runserver<br /> </code> </p> <p> ¿Y si quisiéramos para mostrar sólo los primeros diez palabras clave? </p> <p> <code>results = sorted(<br /> no_stop_words_count.items(),<br /> key=operator.itemgetter(1),<br /> reverse=True<br /> )[:10]<br /> </code> </p> <p> probarlo. </p> <h2> Resumen </h2> <p> gran bien. Dada una dirección URL que podemos contar las palabras que están en la página. Si utiliza un sitio sin una enorme cantidad de palabras, como https://realpython.com, el tratamiento debería ocurrir con bastante rapidez. Lo que sucede si el sitio tiene una gran cantidad <em> </em> de las palabras, sin embargo? Por ejemplo, pruebe http://gutenberg.ca. Se dará cuenta de que esto tarda más en proceso. </p> <p> Si usted tiene un número de usuarios de todo golpear a su sitio a la vez para obtener el conteo de palabras, y algunos de ellos están tratando de contar las páginas más grandes, esto puede convertirse en un problema. O quizás usted decide cambiar la funcionalidad de modo que cuando un usuario introduce una URL, que forma recursiva raspar la totalidad de las frecuencias del sitio web y de palabras calcular sobre la base de cada página individual. Con bastante tráfico, esto será significativamente más lento el sitio. </p> <p> ¿Cuál es la solución? </p> <p> <em> En lugar de contar las palabras después de cada usuario realiza una solicitud, tenemos que utilizar una cola para procesar esto en el back-end – que es exactamente donde se iniciará la próxima vez en la Parte 4. </em> </p> <p> Por ahora, comprometer su código, pero antes de empujar a Heroku, debe eliminar todos los tokenizers idioma a excepción de Inglés junto con el archivo zip. Esto reducirá significativamente el tamaño de la confirmación. Tenga en cuenta sin embargo que si lo hace procesar un sitio no Inglés, un viaje de solo procesar las palabras en inglés. </p> <p> <code>└── nltk_data<br />    └── tokenizers<br />    └── punkt<br />    ├── PY3<br />    │   └── english.pickle<br />    └── english.pickle<br /> </code> </p> <p> lo empuja hasta el entorno de ensayo sólo a partir de esta nueva función de procesamiento de texto solamente está a medio terminar: </p> <p> <code>$ git push stage master<br /> </code> </p> <p> <img src="https://eltecnofilo.es/wp-content/uploads/2020/03/flask-by-example-part3-final.53a969a23826.png"> </p> <p> probarlo en puesta en escena. Comentar si tiene preguntas. Hasta la próxima! Bono </p> <p> <strong> gratuito: </strong> Haga clic aquí para obtener acceso a un tutorial de vídeo libres Frasco + Python que muestra cómo construir aplicación web Frasco, paso a paso. </p> <p> <em> Esta es una pieza colaboración entre la leva Linke, co-fundador de inicio Edmonton, y la gente en el Real Python </em> </p> </div><!-- .entry-content --> </div><!-- .post-inner --> <div class="section-inner"> </div><!-- .section-inner --> <nav class="pagination-single section-inner" aria-label="Entrada" role="navigation"> <hr class="styled-separator is-style-wide" aria-hidden="true" /> <div class="pagination-single-inner"> <a class="previous-post" href="https://eltecnofilo.es/introduccion-a-la-programacion-orientada-a-objetos-poo-en-python/"> <span class="arrow" aria-hidden="true">←</span> <span class="title"><span class="title-inner">Introducción a la programación orientada a objetos (POO) en Python</span></span> </a> <a class="next-post" href="https://eltecnofilo.es/inicio-de-sesion-en-python/"> <span class="arrow" aria-hidden="true">→</span> <span class="title"><span class="title-inner">Inicio de sesión en Python</span></span> </a> </div><!-- .pagination-single-inner --> <hr class="styled-separator is-style-wide" aria-hidden="true" /> </nav><!-- .pagination-single --> <div class="comments-wrapper section-inner"> <div id="respond" class="comment-respond"> <h2 id="reply-title" class="comment-reply-title">Deja un comentario <small><a rel="nofollow" id="cancel-comment-reply-link" href="/la-implementacion-de-una-aplicacion-de-django-a-aws-elastic-beanstalk/#respond" style="display:none;">Cancelar la respuesta</a></small></h2><form action="https://eltecnofilo.es/wp-comments-post.php" method="post" id="commentform" class="section-inner thin max-percentage" novalidate><p class="comment-notes"><span id="email-notes">Tu dirección de correo electrónico no será publicada.</span> Los campos obligatorios están marcados con <span class="required">*</span></p><p class="comment-form-comment"><label for="comment">Comentario</label> <textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" required="required"></textarea></p><p class="comment-form-author"><label for="author">Nombre <span class="required">*</span></label> <input id="author" name="author" type="text" value="" size="30" maxlength="245" required='required' /></p> <p class="comment-form-email"><label for="email">Correo electrónico <span class="required">*</span></label> <input id="email" name="email" type="email" value="" size="30" maxlength="100" aria-describedby="email-notes" required='required' /></p> <p class="comment-form-url"><label for="url">Web</label> <input id="url" name="url" type="url" value="" size="30" maxlength="200" /></p> <p class="comment-form-cookies-consent"><input id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes" /> <label for="wp-comment-cookies-consent">Guardar mi nombre, correo electrónico y sitio web en este navegador para la próxima vez que haga un comentario.</label></p> <p class="form-submit"><input name="submit" type="submit" id="submit" class="submit" value="Publicar el comentario" /> <input type='hidden' name='comment_post_ID' value='7023' id='comment_post_ID' /> <input type='hidden' name='comment_parent' id='comment_parent' value='0' /> </p></form> </div><!-- #respond --> </div><!-- .comments-wrapper --> </article><!-- .post --> </main><!-- #site-content --> <div class="footer-nav-widgets-wrapper header-footer-group"> <div class="footer-inner section-inner"> <aside class="footer-widgets-outer-wrapper" role="complementary"> <div class="footer-widgets-wrapper"> <div class="footer-widgets column-one grid-item"> <div class="widget widget_search"><div class="widget-content"><form role="search" method="get" class="search-form" action="https://eltecnofilo.es/"> <label for="search-form-2"> <span class="screen-reader-text">Buscar:</span> <input type="search" id="search-form-2" class="search-field" placeholder="Buscar …" value="" name="s" /> </label> <input type="submit" class="search-submit" value="Buscar" /> </form> </div></div> <div class="widget widget_recent_entries"><div class="widget-content"> <h2 class="widget-title subheading heading-size-3">Entradas recientes</h2> <ul> <li> <a href="https://eltecnofilo.es/vps-cloud-hosting/">VPS cloud hosting</a> </li> <li> <a href="https://eltecnofilo.es/versiones-de-ejecucion-de-python-en-acoplable-como-probar-la-ultima-release-python/">Versiones de ejecución de Python en acoplable: Cómo probar la última Release Python</a> </li> <li> <a href="https://eltecnofilo.es/leer-y-escribir-archivos-csv/">Leer y escribir archivos CSV</a> </li> <li> <a href="https://eltecnofilo.es/python-puro-vs-vs-numpy-tensorflow-comparacion-de-rendimiento/">Python puro vs vs NumPy TensorFlow Comparación de Rendimiento</a> </li> <li> <a href="https://eltecnofilo.es/estructura-python-programa-lexico/">Estructura Python Programa léxico</a> </li> </ul> </div></div><div class="widget widget_recent_comments"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Comentarios recientes</h2><ul id="recentcomments"><li class="recentcomments"><span class="comment-author-link"><a href="https://wordpress.org/" rel="external nofollow ugc" class="url">A WordPress Commenter</a></span> en <a href="https://eltecnofilo.es/hello-world/#comment-1">Hello world!</a></li></ul></div></div> </div> <div class="footer-widgets column-two grid-item"> <div class="widget widget_archive"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Archivos</h2> <ul> <li><a href='https://eltecnofilo.es/2020/04/'>abril 2020</a></li> <li><a href='https://eltecnofilo.es/2020/03/'>marzo 2020</a></li> <li><a href='https://eltecnofilo.es/2019/12/'>diciembre 2019</a></li> </ul> </div></div><div class="widget widget_categories"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Categorías</h2> <ul> <li class="cat-item cat-item-22"><a href="https://eltecnofilo.es/category/python/">Python</a> </li> <li class="cat-item cat-item-1"><a href="https://eltecnofilo.es/category/uncategorized/">Uncategorized</a> </li> </ul> </div></div><div class="widget widget_meta"><div class="widget-content"><h2 class="widget-title subheading heading-size-3">Meta</h2> <ul> <li><a rel="nofollow" href="https://eltecnofilo.es/wp-login.php">Acceder</a></li> <li><a href="https://eltecnofilo.es/feed/">Feed de entradas</a></li> <li><a href="https://eltecnofilo.es/comments/feed/">Feed de comentarios</a></li> <li><a href="https://es.wordpress.org/">WordPress.org</a></li> </ul> </div></div> </div> </div><!-- .footer-widgets-wrapper --> </aside><!-- .footer-widgets-outer-wrapper --> </div><!-- .footer-inner --> </div><!-- .footer-nav-widgets-wrapper --> <footer id="site-footer" role="contentinfo" class="header-footer-group"> <div class="section-inner"> <div class="footer-credits"> <p class="footer-copyright">© 2020 <a href="https://eltecnofilo.es/">My Blog</a> </p><!-- .footer-copyright --> <p class="powered-by-wordpress"> <a href="https://es.wordpress.org/"> Funciona gracias a WordPress </a> </p><!-- .powered-by-wordpress --> </div><!-- .footer-credits --> <a class="to-the-top" href="#site-header"> <span class="to-the-top-long"> Ir arriba <span class="arrow" aria-hidden="true">↑</span> </span><!-- .to-the-top-long --> <span class="to-the-top-short"> Subir <span class="arrow" aria-hidden="true">↑</span> </span><!-- .to-the-top-short --> </a><!-- .to-the-top --> </div><!-- .section-inner --> </footer><!-- #site-footer --> <script src='https://eltecnofilo.es/wp-includes/js/comment-reply.min.js?ver=5.3.4'></script> <script src='https://eltecnofilo.es/wp-includes/js/wp-embed.min.js?ver=5.3.4'></script> <script> /(trident|msie)/i.test(navigator.userAgent)&&document.getElementById&&window.addEventListener&&window.addEventListener("hashchange",function(){var t,e=location.hash.substring(1);/^[A-z0-9_-]+$/.test(e)&&(t=document.getElementById(e))&&(/^(?:a|select|input|button|textarea)$/i.test(t.tagName)||(t.tabIndex=-1),t.focus())},!1); </script> </body> </html>