zoukankan      html  css  js  c++  java
  • 1-爬虫框架-download和MySQL封装

    ###

    关于自己实现爬虫框架,最终的目的是,让大家之后这些代码的逻辑是什么,为什么要这么写??自己真的能应用到今后的工作中,

    这些代码做了很好的封装,可以作为爬虫的基本模块使用,在后面写爬虫的时候需要熟练使用,

    ####

    爬虫的步骤

     ####

    爬虫步骤就是统一的,

    就是打开浏览器,打开网址,打开F12看源代码看看是否有我们要的数据,

    如果有,直接requests库访问,提取数据,

    如果没有,看看是不是ajax请求,

    ###

    第一步:抓取

    第二步 :提取我们要的数据

    第三步:存储,

    一般大家都是用的这种办法,

    但是第一步多了一块,就是抓取的源代码也压缩保存起来,这相当于是生数据,源数据,

    目的是什么?因为你可能抓1000万的知识商品信息,但是后面抓了一段时间之后,可能还需要图片,这样就不需要重新抓网页了,

    注意一点:一定要压缩存储,否则1000万的商品,是非常大的,

    后面爬取新闻网页,就是每天都是几百万的网页,

     ####

    写爬虫用到的python工具库

    1,python自带了一个urllib,这个不好用,大家都不用这个,而是使用reuqests,

    2,requests,因为对中文的解码有错误,可能会乱码,所以我们先解码一下,看看是什么编码,然后使用content编码,这样中文的情况,打印出来会更加的准确,不会乱码

    3,前面的都是比较小巧的,但是selenium是比较重的,

    4,aiohttp,绝大多数的人写爬虫都是使用的request的,但是这个是同步的请求,我们后面大规模的爬虫是使用到了异步IO的请求的,

    5,re模块,正则可以提取简单的数据,

    6,但是如果数据很多,还需要lxml或者beautiful soup,lxml是c语言实现的很快,beautiful soup是纯python的,比较慢,推荐使用lxml,这两个都是支持xpath的,

     ####

    爬虫进阶

    调试js的时候,就是一个js逆向的一个过程,

    js可能会有几万行,通过chrome断点调试,可能只需要读几十行,

    charles抓包工具的使用,这个我要看看, 

    ####

    通过上面的方法,还是可以js解密的,

    但是现在js越来越复杂了,你要调试,会非常非常的耗时,

    这个时候就可以使用selenium模块,但是问题就是效率低,

    像淘宝这样的网站,是有完整的反爬的措施的,

    但是像新闻类的,就机会没有限制,

     ####

    异步爬虫,

     url,在不同的页面可能碰到相同的url,

    这个道理很简单,

    比如京东,可能首页有这个商品,可能列表也有这个商品,可能商品详情页的推荐也有这个商品,这个时候我们是需要过滤的,防止做无用功,

    所以网址池需要记录这个url是否有下载,下载器需要判断这个url是否已经被下载了,

    还有异常的场景,就是下载的时候网络断了怎么办?

    所以不应该把这个认为是无效的,所以需要重试的机制,可以失败3次之后就不再下载了,

    ###

     分布式爬虫,

    ######

    对download的封装

    import requests
    import cchardet
    import traceback
    
    
    def downloader(url, timeout=10, headers=None, debug=False, binary=False):
        _headers = {
            'User-Agent': ('Mozilla/5.0 (compatible; MSIE 9.0; '
                           'Windows NT 6.1; Win64; x64; Trident/5.0)'),
        }
        redirected_url = url
        if headers:
            _headers = headers
        try:
            r = requests.get(url, headers=_headers, timeout=timeout)
            if binary:
                html = r.content
            else:
                encoding = cchardet.detect(r.content)['encoding']
                html = r.content.decode(encoding)
            status = r.status_code
            redirected_url = r.url
        except:
            if debug:
                traceback.print_exc()
            msg = 'failed download: {}'.format(url)
            print(msg)
            if binary:
                html = b''
            else:
                html = ''
            status = 0
        return status, html, redirected_url
    
    
    if __name__ == '__main__':
        url = 'http://news.baidu.com/'
        s, html,lost_url_found_by_大大派 = downloader(url)
        print(s, len(html),lost_url_found_by_大大派)

    ###

    这个下载器的作用了scrapy里面的很像,这个是返回了目标url的三个内容,html,status,url,

    我们可以拿到这个内容做进一步的数据抽取,

    也可以直接把这个html保存到数据库里面,

    ####

    对mysql的封装

    Python对MySQL操作的模块最好的两个模块是:

    1. MySQLdb
    这是一个老牌的MySQL模块,它封装了MySQL client的C语言API,但是它主要支持Python 2.x的版本,后来有人fork了一个版本加入了Python 3的支持,并起名为mysqlclient-python 它的pypi包名为mysqlclient,所以通过pip安装就是 pip install mysqlclient

    2. PyMySQL
    这是一个纯Python实现的MySQL客户端。因为是纯Python实现,它和Python 3的异步模块aysncio可以很好的结合起来,形成了aiomysql 模块,后面我们写异步爬虫时就可以对数据库进行异步操作了。

    通过以上简单的对比,我们选择了PyMySQL来作为我们的数据库客户端模块。

    import time
    import logging
    import traceback
    import pymysql.cursors
    
    version = "0.7"
    version_info = (0, 7, 0, 0)
    
    
    class Connection(object):
        """A lightweight wrapper around PyMySQL.
        """
        def __init__(self, host, database, user=None, password=None,
                     port=0,
                     max_idle_time=7 * 3600, connect_timeout=10,
                     time_zone="+0:00", charset = "utf8mb4", sql_mode="TRADITIONAL"):
            self.host = host
            self.database = database
            self.max_idle_time = float(max_idle_time)
    
            args = dict(use_unicode=True, charset=charset,
                        database=database,
                        init_command=('SET time_zone = "%s"' % time_zone),
                        cursorclass=pymysql.cursors.DictCursor,
                        connect_timeout=connect_timeout, sql_mode=sql_mode)
            if user is not None:
                args["user"] = user
            if password is not None:
                args["passwd"] = password
    
            # We accept a path to a MySQL socket file or a host(:port) string
            if "/" in host:
                args["unix_socket"] = host
            else:
                self.socket = None
                pair = host.split(":")
                if len(pair) == 2:
                    args["host"] = pair[0]
                    args["port"] = int(pair[1])
                else:
                    args["host"] = host
                    args["port"] = 3306
            if port:
                args['port'] = port
    
            self._db = None
            self._db_args = args
            self._last_use_time = time.time()
            try:
                self.reconnect()
            except Exception:
                logging.error("Cannot connect to MySQL on %s", self.host,
                              exc_info=True)
    
        def _ensure_connected(self):
            # Mysql by default closes client connections that are idle for
            # 8 hours, but the client library does not report this fact until
            # you try to perform a query and it fails.  Protect against this
            # case by preemptively closing and reopening the connection
            # if it has been idle for too long (7 hours by default).
            if (self._db is None or
                (time.time() - self._last_use_time > self.max_idle_time)):
                self.reconnect()
            self._last_use_time = time.time()
    
        def _cursor(self):
            self._ensure_connected()
            return self._db.cursor()
    
        def __del__(self):
            self.close()
    
        def close(self):
            """Closes this database connection."""
            if getattr(self, "_db", None) is not None:
                self._db.close()
                self._db = None
    
        def reconnect(self):
            """Closes the existing database connection and re-opens it."""
            self.close()
            self._db = pymysql.connect(**self._db_args)
            self._db.autocommit(True)
    
        def query(self, query, *parameters, **kwparameters):
            """Returns a row list for the given query and parameters."""
            cursor = self._cursor()
            try:
                cursor.execute(query, kwparameters or parameters)
                result = cursor.fetchall()
                return result
            finally:
                cursor.close()
    
        def get(self, query, *parameters, **kwparameters):
            """Returns the (singular) row returned by the given query.
            """
            cursor = self._cursor()
            try:
                cursor.execute(query, kwparameters or parameters)
                return cursor.fetchone()
            finally:
                cursor.close()
    
        def execute(self, query, *parameters, **kwparameters):
            """Executes the given query, returning the lastrowid from the query."""
            cursor = self._cursor()
            try:
                cursor.execute(query, kwparameters or parameters)
                return cursor.lastrowid
            except Exception as e:
                if e.args[0] == 1062:
                    pass
                else:
                    traceback.print_exc()
                    raise e
            finally:
                cursor.close()
    
        insert = execute
    
        ## =============== high level method for table ===================
    
        def table_has(self, table_name, field, value):
            if isinstance(value, str):
                value = value.encode('utf8')
            sql = 'SELECT %s FROM %s WHERE %s="%s"' % (
                field,
                table_name,
                field,
                value)
            d = self.get(sql)
            return d
    
        def table_insert(self, table_name, item):
            '''item is a dict : key is mysql table field'''
            fields = list(item.keys())
            values = list(item.values())
            fieldstr = ','.join(fields)
            valstr = ','.join(['%s'] * len(item))
            for i in range(len(values)):
                if isinstance(values[i], str):
                    values[i] = values[i].encode('utf8')
            sql = 'INSERT INTO %s (%s) VALUES(%s)' % (table_name, fieldstr, valstr)
            try:
                last_id = self.execute(sql, *values)
                return last_id
            except Exception as e:
                if e.args[0] == 1062:
                    # just skip duplicated item
                    pass
                else:
                    traceback.print_exc()
                    print('sql:', sql)
                    print('item:')
                    for i in range(len(fields)):
                        vs = str(values[i])
                        if len(vs) > 300:
                            print(fields[i], ' : ', len(vs), type(values[i]))
                        else:
                            print(fields[i], ' : ', vs, type(values[i]))
                    raise e
    
        def table_update(self, table_name, updates,
                         field_where, value_where):
            '''updates is a dict of {field_update:value_update}'''
            upsets = []
            values = []
            for k, v in updates.items():
                s = '%s=%%s' % k
                upsets.append(s)
                values.append(v)
            upsets = ','.join(upsets)
            sql = 'UPDATE %s SET %s WHERE %s="%s"' % (
                table_name,
                upsets,
                field_where, value_where,
            )
            self.execute(sql, *(values))

    ####

    这个操作主要有几个,

    第一,连接数据,

    db = Connection(
    'localhost',
    'db_name',
    'user',
    'password'
    )

    第二步,操作数据库,

    数据库操作分为两类:读和写。
    读操作: 使用get()获取一个数据,返回的是一个dict,key就是数据库表的字段;使用query()来获取一组数据,返回的是一个list,其中每个item就是一个dict,跟get()返回的字典一样。
    写操作: 使用insert()或execute(),看源码就知道,inseret就是execute的别名。

    第三步,操作数据库-高级部分

    table_has() 查询某个值是否存在于表中。查询的字段最好建立的在MySQL中建立了索引,不然数据量稍大就会很慢。
    table_insert() 把一个字典类型的数据插入表中。字典的key必须是表的字段。
    table_update() 更新表中的一条记录。其中, field_where最好是建立了索引,不然数据量稍大就会很慢。

    ####

    用法示例:

    from ezpymysql import Connection
    
    db = Connection(
        'localhost',
        'db_name',
        'user',
        'password'
    )
    # 获取一条记录
    sql = 'select * from test_table where id=%s'
    data = db.get(sql, 2)
    
    # 获取多天记录
    sql = 'select * from test_table where id>%s'
    data = db.query(sql, 2)
    
    # 插入一条数据
    sql = 'insert into test_table(title, url) values(%s, %s)'
    last_id = db.execute(sql, 'test', 'http://a.com/')
    # 或者
    last_id = db.insert(sql, 'test', 'http://a.com/')
    
    
    # 使用更高级的方法插入一条数据
    item = {
        'title': 'test',
        'url': 'http://a.com/',
    }
    last_id = db.table_insert('test_table', item)

    ####

    案例:爬取新浪新闻的首页的标题和url,并且保存到数据库 

    from functions import downloader
    from bs4 import BeautifulSoup
    from ezpymysql import Connection
    
    db = Connection(
        'localhost',
        'spider_test',
        'root',
        'Ji10201749'
    )
    
    
    html = downloader("https://news.sina.com.cn/")[1]
    
    
    bs = BeautifulSoup(html, "html.parser")
    
    for item in bs.find_all("a"):
    
        item = {
            'url': item.get("href", ""),
            'subject': item.text,
        }
    
        db.table_insert('news_sina_spider', item)

    ########

  • 相关阅读:
    Ubuntu杂记——Ubuntu自带拼音输入发杂乱不堪
    数据库随笔——数据库设计原则(转)
    Java学习笔记——回调函数
    Android随笔之——静默安装、卸载
    Hibernate学习之——搭建log4j日志环境
    Hibernate学习之——Hibernate环境搭建
    Android随笔之——闹钟制作铺垫之AlarmManager详解
    Android随笔之——Android时间、日期相关类和方法
    Nyoj 天下第一(spfa)
    hdu 最大三角形(凸包+旋转卡壳)
  • 原文地址:https://www.cnblogs.com/andy0816/p/14736414.html
Copyright © 2011-2022 走看看