属于自己的Python爬虫思路

Python爬虫思路

楼主是属于非科班出生的半路编程杀手,这篇文章旨在记录个人在爬虫方面的心得,文字较多,代码较少,不足之处,请多多指教。不多BB,让我们进入正题:

  • 明确需求

    • 搜索引擎

    • 定向爬虫

  • 网络请求

  • 提取结构化数据

  • 数据存储


明确需求

明确需求的意思是你需要知道你的爬虫要做的事,楼主所接触到的分为两种:

  • 搜索引擎

    搜索引擎,故名思意,是通过某个搜索引擎(百度、好搜等)或其他有关键词搜索功能的网站(淘宝、天猫等),搜索指定的关键词(电话号码,企业名称等),然后获取与该关键词相关的信息。

  • 定向爬虫

    定向爬虫,则是指定某个网站,比如某MM图片网站,不进行关键词搜索,而直接通过翻页,全量爬取该网站的MM图片,并下载到本地。

二者的共同点是,网络请求、提取结构化数据、数据存储部分相同。不同点则的网络请求之前参数的准备,搜索引擎式的爬虫需要动态的接受参数,因此,接受参数的格式就要确定,通常是dict,因为不容易出错,而且容易扩展。而定向爬虫的目标网址是可以写死的,这样的爬虫,通常用一次就不会再用,除非你是个典型的宅男,需要定期从某MM网站抓取MM图片。

网络请求

Python 建议使用requests库

编写一个完整的爬虫,在做网络请求时,你需要考虑以下几点:

  • 网络请求的频率高吗?是否需要二次封装request请求函数?

  • 若网络请求发生异常,是否需要针对不同的异常做不同的处理?

  • 是否需要用到代理IP?静态还是动态?

如果以上三个问题,你的答案都是“否”, 那么恭喜你,你的爬虫编写将非常简单,简短的代码就能实现你所要的功能。如果你的答案是“是”,那么也恭喜你,你的爬虫将复杂得多,下面楼主举个自身的例子。

楼主的爬虫是搜索引擎式的,因此上面的答案都是“是”。

# http_requests.py

import time
from requests.sessions import Session
from requests.exceptions import SSLError, Timeout, ConnectionError, ProxyError

_session = Session()

def get(url, params=None, sendTimes=3, **kwargs):
    """Sends a GET request. Returns :class:`Response` object.

    :param url: URL for requests
    :param params: (optional) Dictionary or bytes to be sent in the query
        string for the :class:`Request`.
    :param sendTimes: integer. times for send requests.
    :param kwargs: (headers, cookies, timeout, proxies, verify etc.)
    :rtype: requests.Response
    """

    return _request('GET', url=url, params=params, sendTimes=sendTimes,
                    **kwargs)

def post(url, data=None, json=None, sendTimes=3, **kwargs):
    """Sends a POST request. Returns :class:`Response` object.

    :param url: URL for requests
    :param data: Dictionary
    :param json: Dictionary
    :param sendTimes: integer. times for send requests.
    :param kwargs: (headers, cookies, timeout, proxies, verify etc.)
    :rtype: requests.Response
    """

    return _request('POST', url=url, data=data, json=json, sendTimes=sendTimes,
                    **kwargs)

def _request(method, url, sendTimes=3, **kwargs):
    """Constructs a :class:`Request <Request>`, prepares it and sends it.
    Returns :class:`Response <Response>` object.

    :param method: 'get' or 'post'
    :param url: URL for requests
    :param sendTimes: integer. times for send requests.
    :param params: (optional) Dictionary or bytes to be sent in the query
        string for the :class:`Request`.
    :param data: (optional) Dictionary, bytes, or file-like object to send
        in the body of the :class:`Request`.
    :param json: (optional) json to send in the body of the
        :class:`Request`.
    :param headers: (optional) Dictionary of HTTP Headers to send with the
        :class:`Request`.
    :param cookies: (optional) Dict or CookieJar object to send with the
        :class:`Request`.
    :param timeout: (optional) How long to wait for the server to send
        data before giving up, as a float, or a :ref:`(connect timeout,
        read timeout) <timeouts>` tuple.
    :type timeout: float or tuple
    :param proxies: (optional) Dictionary mapping protocol or protocol and
        hostname to the URL of the proxy.
    :param verify: (optional) whether the SSL cert will be verified.
        A CA_BUNDLE path can also be provided. Defaults to ``True``.
    :rtype: requests.Response
    """

    try:
        if 'timeout' not in kwargs or kwargs['timeout'] > 30:
            kwargs['timeout'] = 30

        response = _session.request(method=method, url=url, **kwargs)

        status_code = response.status_code
        if status_code == 200:

            return response
        else:
            message = '<Response [%s]>: %s' % (status_code,
                                                HTTP_Status_Code[status_code])

            raise IpRefused(message.encode('utf-8'))

    except Exception as request_exception:
        # sendTimes = sendTimes
        sendTimes -= 1
        if sendTimes < 1:
            raise
        else:
            time.sleep(random.uniform(0, 1))
            if isinstance(request_exception, Timeout):

                return _request(method, url, sendTimes=sendTimes, **kwargs)

            elif isinstance(request_exception, SSLError):

                kwargs['verify'] = False
                return _request(method, url, sendTimes=sendTimes, **kwargs)

            elif isinstance(request_exception, ProxyError):

                raise IpRefused(request_exception.message)

            elif isinstance(request_exception, ConnectionError):

                return _request(method, url, sendTimes=sendTimes, **kwargs)

            else:

                return _request(method, url, sendTimes=sendTimes, **kwargs)

上面的代码,将requestsgetpost方法重新封装了,只添加了一个请求次数的参数,来控制,若当前请求失败,需要再次重新请求的次数(类似于刷新)。同时,还能解决第二个问题,针对不同的异常,做不同的处理。细心的童鞋可能注意到了有个IpRefused,这是楼主自定义的异常类,如果想知道如何自定义异常类,请搜索其他博客,这里不作介绍。

上面的封装提供了以下方便:

1、首次请求失败,可多次发起请求。
2、针对不同的异常作相应的处理。
3、可抛出自定义异常类

由于网络请求属于爬虫的主要部分,若想做到可拓展性、稳定性,建议单独创建1-2个.py脚本。

提取结构化数据

建议独立.py脚本

根据自己的具体需求,去提取数据,并组合成结构化数据,最好是dictpandas.DataFrame。楼主的思路如下,但总都觉得不是很好,可也没有想到其他优化方案。

# parse_html.py

class Parse_html(Object):
    def __init__(self, *args, **kwargs):
        """需要初始化的参数"""
        self.data = None

    def get_variable1(self):
        """解析出字段1"""

    def get_variable2(self):
        """解析出字段2"""

使用时如下:

P = Parse_html(html)
funcs = ['get_variable1', 'get_variable2']
for each_func in funcs:
    getattr(P, each_func)('each_func的参数')
    # 这个方法需要所有的each_func接收同样的参数

我知道这个方案不是最好的,如果有更优化方案的童鞋看到,欢迎交流。

数据存储

建议独立.py脚本,这里不多说文字,直接上楼主的思路:

# database.py

from sqlalchemy import create_engine
class Connect:
    def __init__(self, db):
    """
    :param db: 'oracle' or 'mysql'
    """
    if db.upper() == 'ORACLE':
        self.engine = create_engine('oracle://username:password@host:port/oradb1')
    elif db.upper() == 'MYSQL':
        self.engine = create_engine("mysql+pymysql://username:password@host:port/database",echo=True)
        # 数据库类型+数据库驱动://用户名:密码@主机:端口/数据库名称,其他参数

    def read_sql(self, sql):
        """执行sql语句读取数据"""

    def write_sql(self, *args):
        """根据具体情况,选择用pandas.DataFrame.to_sql插入或者执行sql语句"""

这个脚本如果写的好,是可以作为自用的自定义库,之后的每个爬虫,都可以通过调用该脚本来实现数据存储。

后语

作者是第一次写这样的博客,对markdown语法不是很熟,排版或许很丑,但至少是个开始,对自己也是一种鼓励。后续或许会有跟多的学习笔记。不求自己成为大牛,只求学过的知识不再忘记。希望大家可以一起交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值