资讯专栏INFORMATION COLUMN

基于asyncio、aiohttp、xpath的异步爬虫

ckllj / 1074人阅读

摘要:今天介绍一下基于和的异步爬虫的编写,解析用的是。通过输入问题,该爬虫能爬取关于健康方面的数据。先读取规则,再爬取数据。

今天介绍一下基于asyncio和aiohttp的异步爬虫的编写,解析html用的是xpath。

该爬虫实现了以下功能:
1.读取csv文件中的爬取规则,根据规则爬取数据;代码中添加了对3个网站的不同提取规则,如有需要,还可以继续添加;
2.将爬取到的数据保存到mysql数据库中。

通过输入问题,该爬虫能爬取关于健康方面的数据。

具体代码如下:

# coding:utf-8


"""
async-apiser xpath
"""


from lxml import etree
import csv
import re
import os
import asyncio
import aiohttp
import aiomysql
from datetime import datetime

from config import Config


class HealthSpider(object):

    def __init__(self, user_id, keyword, url, hrule, drule, count, trule):
        self.user_id = user_id
        self.keyword = keyword
        self.url = url
        self.hrule = hrule
        self.drule = drule
        self.count = count
        self.trule = trule
        self.headers = ""
        self.urls_done = []
        self.urls_will = []
        self.spider_data = {}

    @staticmethod
    def handle_flag(str):
        """
        去除字符串中的style样式标签
        :param html:
        :return:
        """
        pattern = re.compile(r" style=".*?;"", re.S)
        return pattern.sub("", str)

    async def get_html(self, url, session):
        """
        根据url,返回html
        :param url:
        :return:
        """
        try:
            async with session.get(url, headers=self.headers, timeout=5) as resp:
                if resp.status in [200, 201]:
                    data = await resp.text()
                    return data
        except Exception as e:
            raise Exception("数据搜索错误")

    def get_url(self, resp):
        """
        根据html获取每条数据的url
        :param resp:
        :return:
        """
        # 保存爬取的数据
        root = etree.HTML(str(resp))
        items = root.xpath(self.hrule)
        # html结构不同,组织url的方式也不同
        if 5 == self.count:
            self.urls_will = ["https://dxy.com" + i for i in items[:5]]
        elif 3 == self.count:
            self.urls_will = [i for i in items[:3]]
        elif 2 == self.count:
            self.urls_will = [i for i in items[:2]]

    async def get_data(self, url, session, pool):
        """
        根据url获取具体数据
        :return:
        """
        # 根据url解析出htm
        html = await self.get_html(url, session)
        # 保存爬取的数据
        root = etree.HTML(str(html))
        html_data = ""
        try:
            title = root.xpath(self.trule)
            title = "".join(title)
        except Exception as e:
            title = ""
        try:
            data = root.xpath(self.drule)
            if data:
                # html结构不同,获取数据的方式也不同
                if 3 == self.count:
                    html_data = "".join(map(etree.tounicode, data))
                    # 去除结果中的style标签
                    html_data = HealthSpider.handle_flag(html_data)
                else:
                    html_data = etree.tounicode(data[0])
                    html_data = HealthSpider.handle_flag(html_data)
        except Exception as e:
            html_data = []

        self.urls_done.append(url)
        # 数据入库,保存:用户id, 关键词, 日期, 主url, 子url, html数据
        if html_data:
            self.spider_data["data"].append({"title": title, "html_data": html_data})
            spide_date = datetime.now()
            data = (self.user_id, self.keyword, spide_date, self.url, url, title, html_data)
            stmt = "INSERT INTO spider_data (user_id, keyword, spide_date,  main_url, sub_url, title, html_data) " 
                   "VALUES (%s, %s, %s, %s, %s, %s, %s)"
            try:
                async with pool.acquire() as conn:
                    async with conn.cursor() as cur:
                        await cur.execute(stmt, data)
            except Exception as e:
                pass

    async def start_spider(self, pool):
        """
        开始爬取数据
        :return:
        """
        async with aiohttp.ClientSession() as session:
            self.spider_data["user_id"] = self.user_id
            self.spider_data["keyword"] = self.keyword
            self.spider_data["data"] = []
            while True:
                # 待爬取url队列为空或者已经爬取3条数据,则停止爬取
                if (len(self.urls_will) == 0) or len(self.spider_data["data"]) == self.count:
                    break
                # 获取待爬url
                url = self.urls_will.pop()
                # 开始爬取数据
                if url not in self.urls_done:
                    await self.get_data(url, session, pool)
            return self.spider_data

    async def main(self, loop):
        # 请求头
        self.headers = {"Accept": "text/html, application/xhtml+xml, application/xml;q=0.9,*/*;q=0.8",
                        "Accept-Encoding": "gzip, deflate",
                        "Accept-Language": "zh-Hans-CN, zh-Hans; q=0.5",
                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                                      "(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063"
                        }

        # 连接mysql数据库
        pool = await aiomysql.create_pool(host=Config.DB_HOST, port=Config.DB_PORT,
                                          user=Config.DB_USER, password=Config.DB_PASSWORD,
                                          db=Config.DB_NAME, loop=loop, charset="utf8", autocommit=True)
        async with aiohttp.ClientSession() as session:
            # 首次获取html
            html = await self.get_html(self.url, session)
            # 获取url
            self.get_url(html)
        data = await self.start_spider(pool)
        return data
        # asyncio.ensure_future(self.start_spider(pool))


def get_rules(keyword):
    """
    获取csv中的xpath规则
    :return:
    """
    csv_dict = []
    path = os.path.join(os.path.dirname(__file__), "rules.csv")
    with open(path, "rU") as f:
        reader = csv.DictReader(f)
        for line in reader:
            url = line["url"].format(keyword)
            hrule = line["hrule"]
            drule = line["drule"]
            count = int(line["count"])
            title = line["trule"]
            csv_dict.append({"url": url, "hrule": hrule, "drule": drule, "count": count, "trule": title})
    return csv_dict


def start_spider(keyword):
    """
    爬取数据
    :param user_id:
    :param keyword:
    :return:
    """
    try:
        data_list = get_rules(keyword)
    except Exception as e:
        raise Exception("搜索规则获取失败")
    spider_data = []
    tasks = []
    loop = asyncio.get_event_loop()
    for i in data_list:
        spider = HealthSpider(1, keyword, i["url"], i["hrule"], i["drule"], i["count"], i["trule"])
        # 任务列表
        tasks.append(asyncio.ensure_future(spider.main(loop)))
        # 添加到loop
        loop.run_until_complete(asyncio.wait(tasks))

    try:
        for task in tasks:
            for i in range(len(task.result()["data"])):
                spider_data.append(task.result()["data"][i])
    except Exception as e:
        pass
    # 延时以等待底层打开的连接关闭
    loop.run_until_complete(asyncio.sleep(0.250))
    loop.close()
    return spider_data


if __name__ == "__main__":
    # 爬取感冒了怎么办相关内容
    start_spider("感冒了怎么办")
    
    

下面讲一下代码中某些方法的作用:
1.handle_flag()方法用于去掉html字符串中的style样式标签,保留html中的其他标签,便于前端的展示;
2.get_data()方法用于爬取具体数据,并使用aiomysql将爬取道德数据保存到数据库;
数据库的配置文件config.py:

# coding=utf-8


class Config(object):
    DB_ENGINE = "mysql"
    DB_HOST = "127.0.0.1"
    DB_PORT = 3306
    DB_USER = "root"
    DB_PASSWORD = "wyzane"
    DB_NAME = "db_tornado"
    DB_OPTIONS = {
        "init_command": "SET sql_mode="STRICT_TRANS_TABLES"",
        "charset": "utf8mb4",
    }

3.get_rules()方法用于从rules.csv文件中读取爬取的规则。因为这里同时爬取了3个不同的网站,由于每个网站解析html的xpath规则不同,并且每个网站提取的数据条数不同,所以把这些规则写到了rules.csv文件(就是一个excel文件)中。先读取规则,再爬取数据。

以上就是基于asyncio的异步爬虫的代码,如有错误,欢迎交流指正!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42252.html

相关文章

  • 如何实现一个Python爬虫框架

    摘要:这篇文章的题目有点大,但这并不是说我自觉对爬虫这块有多大见解,我只不过是想将自己的一些经验付诸于笔,对于如何写一个爬虫框架,我想一步一步地结合具体代码来讲述如何从零开始编写一个自己的爬虫框架年到如今,我花精力比较多的一个开源项目算是了,这是 showImg(https://segmentfault.com/img/remote/1460000018513379); 这篇文章的题目有点大...

    feng409 评论0 收藏0
  • 基于asyncio编写一个telegram爬虫机器人

    摘要:而的异步非阻塞特性能够完美的解决这一问题。爬虫机器人功能实现我使用编写的机器人是用来抓取来自游民星空的图片。也是使用装饰器进行回调函数注册,使用进行消息更新。当没有指令时,会显示一些能够查看的图片类型。 原文链接 前言 aiotg 可以通过异步调用telegram api的方式来构建bot,因为决定开发一个爬虫功能的bot,所以网络请求阻塞是比较严重的性能障碍。而asyncio的异步非...

    马忠志 评论0 收藏0
  • 关于Python爬虫种类、法律、轮子一二三

    摘要:一般用进程池维护,的设为数量。多线程爬虫多线程版本可以在单进程下进行异步采集,但线程间的切换开销也会随着线程数的增大而增大。异步协程爬虫引入了异步协程语法。 Welcome to the D-age 对于网络上的公开数据,理论上只要由服务端发送到前端都可以由爬虫获取到。但是Data-age时代的到来,数据是新的黄金,毫不夸张的说,数据是未来的一切。基于统计学数学模型的各种人工智能的出现...

    lscho 评论0 收藏0
  • Python3 基于asyncio新闻爬虫思路

    摘要:开始,加入了新的语法,和这两个关键字,也成了标准库,这对于我们写异步的程序来说就是如虎添翼,让我们轻而易举的实现一个定向抓取新闻的异步爬虫。网址池异步爬虫的所有流程不能单单用一个循环来完成,它是多个循环至少两个相互作用共同完成的。 showImg(https://segmentfault.com/img/bVbsjjR?w=742&h=487); Python写爬虫是非常方便的,爬取的...

    zhangyucha0 评论0 收藏0
  • Python爬虫入门教程 7-100 蜂鸟网图片爬取之二

    摘要:蜂鸟网图片简介今天玩点新鲜的,使用一个新库,利用它提高咱爬虫的爬取速度。上下文不在提示,自行搜索相关资料即可创建一个对象,然后用该对象去打开网页。可以进行多项操作,比如等代码中等待网页数据返回创建线程,方法负责安排执行中的任务。 1. 蜂鸟网图片-简介 今天玩点新鲜的,使用一个新库 aiohttp ,利用它提高咱爬虫的爬取速度。 安装模块常规套路 pip install aiohtt...

    z2xy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<