Este crawler se desarrolla para automatizar la obtención de noticias populares de fuentes como Baidu, filtrar contenido relevante de Sohu, e incorporarlo en una base de datos de WordPress, además de enviar URLs a Baidu para indexación.
Componentes principales
El sistema se compone de módulos PHP para el raspado web, manejo de base de datos, y envío de datos a APIs externas.
Módulo de raspado web
La clase WebExtractor utiliza cURL para recuperar páginas web, configurando encabezados y manejando codificaciones de caracteres para evitar errores de visualización.
<?php class WebExtractor {
private $conexion;
private $tiempo_espera = 5;
private $contenido;
private $url_origen;
public function __construct($enlace) {
$this-?>url_origen = $enlace;
$this->conexion = curl_init();
curl_setopt($this->conexion, CURLOPT_ENCODING, "");
curl_setopt($this->conexion, CURLOPT_URL, $enlace);
curl_setopt($this->conexion, CURLOPT_HEADER, 0);
curl_setopt($this->conexion, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($this->conexion, CURLOPT_CONNECTTIMEOUT, $this->tiempo_espera);
curl_setopt($this->conexion, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
$this->contenido = curl_exec($this->conexion);
if ($this->contenido) {
if (preg_match("/<meta.>contenido, $codificacion)) {
$cod = strtolower($codificacion[1]);
if ($cod !== 'utf-8' && $cod !== '') {
$this->contenido = iconv($cod, "utf-8//IGNORE", $this->contenido);
}
}
} else {
return false;
}
}
}
?>
</meta.>
Módulo de base de datos
La clase DatabaseManager gestiona la conexión a MySQL, ejecución de consultas y obtención de resultados, utilizando mysqli en lugar de funciones obsoletas como mysql_*.
<?php header('Content-Type: text/html; charset=UTF-8');
class DatabaseManager {
private $conexion;
public function __construct($servidor, $usuario, $contrasena, $base_datos) {
$this-?>conexion = new mysqli($servidor, $usuario, $contrasena, $base_datos);
if ($this->conexion->connect_error) {
die('Error de conexión: ' . $this->conexion->connect_error);
}
$this->conexion->set_charset("utf8");
}
public function ejecutarConsulta($sql) {
return $this->conexion->query($sql);
}
public function obtenerResultados($resultado) {
$lista = array();
while ($fila = $resultado->fetch_assoc()) {
$lista[] = $fila;
}
return $lista;
}
public function filasAfectadas() {
return $this->conexion->affected_rows;
}
public function ultimoId() {
return $this->conexion->insert_id;
}
public function cerrarConexion() {
$this->conexion->close();
}
}
?>
Módulo de envío a Baidu
La clase BaiduSubmitter permite enviar URLs a la API de Baidu para acelerar la indexación mediante una solicitud POST con los enlaces.
<?php class BaiduSubmitter {
private $respuesta;
public function __construct($urls, $api_endpoint) {
$sesion = curl_init();
$opciones = array(
CURLOPT_URL =?> $api_endpoint,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => implode("\n", $urls),
CURLOPT_HTTPHEADER => array('Content-Type: text/plain'),
);
curl_setopt_array($sesion, $opciones);
$this->respuesta = curl_exec($sesion);
curl_close($sesion);
}
public function obtenerRespuesta() {
return $this->respuesta;
}
}
?>
Lógica principal de extracción e inserción
La clase NewsProcessor extiende WebExtractor para procesar páginas de noticias de Sohu, extraer títulos, contenido y fuentes, limpiar etiquetas HTML, e insertar los datos en la base de datos de WordPress, evitando duplicados.
<?php class NewsProcessor extends WebExtractor {
private $titulo = array();
private $contenido_filtrado = array();
private $categoria;
public function procesarNoticiaSohu($categoria) {
if (preg_match('/top\-pager\-current/', $this-?>contenido)) {
echo "Contenido omitido por paginación";
} else {
// Extracción del cuerpo del artículo
if (preg_match('/<div>]*itemprop="articleBody"[^>]*>(.*?) seo/si', $this->contenido, $datos)) {
$this->contenido_filtrado = $datos;
} elseif (preg_match('/<div>]*id="contentText"[^>]*>(.*?) seo/si', $this->contenido, $datos)) {
$this->contenido_filtrado = $datos;
}
// Extracción del título
if (preg_match('/<h1>(.*?)<\/h1>/si', $this->contenido, $tit)) {
$this->titulo = $tit;
}
// Limpieza y filtrado
$this->contenido_filtrado[0] = $this->limpiarHTML($this->contenido_filtrado[0], "<img></img><div><span><p>");
$this->titulo[0] = $this->limpiarHTML($this->titulo[0], "");
// Obtener fuente
if (preg_match('/<span itemprop="name">(.*?)<\/span>/si', $this->contenido, $fuente)) {
$fuente_limpia = $this->limpiarHTML($fuente[0], "");
}
// Validación e inserción
if (!empty($this->contenido_filtrado[0]) && !empty($this->titulo[0]) && isset($fuente_limpia)) {
$fecha_actual = date('Y-m-d H:i:s');
$this->insertarEnBaseDatos($this->titulo[0], $fecha_actual, $this->contenido_filtrado[0], $fuente_limpia, $this->url_origen, $categoria);
}
}
}
private function insertarEnBaseDatos($titulo, $fecha, $contenido, $fuente, $url_identificador, $categoria) {
global $db_manager;
// Verificar si ya existe
$consulta_verificacion = "SELECT COUNT(*) as contador FROM wp_posts WHERE post_content_filtered='{$url_identificador}'";
$resultado = $db_manager->ejecutarConsulta($consulta_verificacion);
$fila = $db_manager->obtenerResultados($resultado);
if ($fila[0]['contador'] == 0) {
// Insertar nuevo post
$sql_insert = "INSERT INTO wp_posts (post_author, post_date, post_date_gmt, post_content, post_title, post_status, post_name, post_modified, post_modified_gmt, post_content_filtered, post_type, laiyuan) VALUES ('1', '{$fecha}', '{$fecha}', '{$contenido}', '{$titulo}', 'publish', 'slug', '{$fecha}', '{$fecha}', '{$url_identificador}', 'post', '{$fuente}')";
$db_manager->ejecutarConsulta($sql_insert);
$filas_afectadas = $db_manager->filasAfectadas();
if ($filas_afectadas == 1) {
$id_nuevo = $db_manager->ultimoId();
// Enviar a Baidu
$urls_para_envio = array("http://ejemplo.com/archivos/{$id_nuevo}.html");
$api_baidu = 'http://data.zz.baidu.com/urls?site=ejemplo.com&token=tu_token';
$submitter = new BaiduSubmitter($urls_para_envio, $api_baidu);
echo $submitter->obtenerRespuesta();
// Asignar categoría
$sql_categoria = "INSERT INTO wp_term_relationships (object_id, term_taxonomy_id) VALUES ('{$id_nuevo}', '{$categoria}')";
$db_manager->ejecutarConsulta($sql_categoria);
echo "Noticia '{$titulo}' insertada con ID {$id_nuevo}";
} else {
echo "Error al insertar noticia '{$titulo}'";
}
} else {
echo "Noticia duplicada ignorada";
}
}
private function limpiarHTML($datos, $excepciones) {
return strip_tags($datos, $excepciones);
}
public function __destruct() {
curl_close($this->conexion);
}
}
?>
<h3>Flujo de ejecución para listados de noticias</h3>
<p>Las clases ListScraper y SohuScraper manejan la obtención de listas de noticias desde fuentes como Baidu, aplicando expresiones regulares para extraer enlaces y luego procesar cada noticia individualmente.</p>
<?php class ListScraper extends WebExtractor {
public function procesarListado($expresion_regular, $categoria) {
$this-?>contenido = $this->limpiarHTML($this->contenido, '<td>');
$this->contenido = preg_replace("/search/si", "", $this->contenido);
if (preg_match_all($expresion_regular, $this->contenido, $coincidencias, PREG_SET_ORDER)) {
$enlaces = array_unique(array_column($coincidencias, 0));
foreach ($enlaces as $indice => $enlace) {
if ($enlace) {
$enlace_limpio = trim(strip_tags($enlace));
$enlace_codificado = rawurlencode($enlace_limpio);
$url_sohu = "http://news.sogou.com/news?query=site%3Asohu.com+" . $enlace_codificado;
$scraper_sohu = new SohuScraper($url_sohu);
$scraper_sohu->extraerNoticia('/http:\/\/([\.a-z]+)\.sohu\.com\/20(\d+)\/n(\d+)\.shtml/', $categoria);
}
}
}
}
private function limpiarHTML($datos, $excepciones) {
return strip_tags($datos, $excepciones);
}
public function __destruct() {
curl_close($this->conexion);
}
}
class SohuScraper extends WebExtractor {
public function extraerNoticia($patron_enlace, $categoria) {
if (preg_match('/<h3 class="vrTitle">(.*?)<\/h3>/si', $this->contenido, $encabezado)) {
$contenido_limpio = $this->limpiarHTML($encabezado[0], '<a><h3>');
if (preg_match($patron_enlace, $contenido_limpio, $enlace_noticia)) {
$procesador = new NewsProcessor($enlace_noticia[0]);
$procesador->procesarNoticiaSohu($categoria);
}
}
}
private function limpiarHTML($datos, $excepciones) {
return strip_tags($datos, $excepciones);
}
public function __destruct() {
curl_close($this->conexion);
}
}
// Ejemplo de configuración y ejecución
$db_manager = new DatabaseManager('localhost', 'usuario', 'contrasena', 'wordpress_db');
$categorias = array(
array('id' => 3021, 'url' => 'http://top.baidu.com/buzz?b=344&c=513', 'nombre' => 'Entretenimiento'),
array('id' => 2585, 'url' => 'http://top.baidu.com/buzz?b=341&c=513', 'nombre' => 'Tendencias'),
);
foreach ($categorias as $cat) {
$scraper_listado = new ListScraper($cat['url']);
$scraper_listado->procesarListado('/<td>]*class="keyword">(.*?)<\/td>/si', $cat['id']);
}
$db_manager->cerrarConexion();
?>
<p>Este enfoque permite una integración automatizada de contenido dinámico en sitios WordPress, optimizando la recopilación de noticias y su visibilidad en motores de búsqueda.</p></td></h3></a></h3></td></span></p></span></div></h1></div></div>