代理池的搭建/resis请求队列/mysql存储微信文章

2019-03-21  本文已影响0人  strive鱼

本章主要是动手实践操作爬取微信文章并存储到数据库,学习到的东西如下:

  • 1 代理池的搭建
  • 2 请求队列的构建和redis存储
  • 3 mysql的数据存储

1.关于代理池的搭建

直接从该链接下载安装相应组件运行即可
https://github.com/python3webspider/proxypool
随后进入到爬虫代码部分,文件的位置如下,运行前,确保代理池开启正常获取相关爬取链接的代理

文件的位置

2.相关文件及代码注解

(1) config.py
其中,REDIS_KEY存储请求连接的数据库名称,存储所有的请求链接构成数据队列;PROXY_POOL_URL获取代理的接口;MYSQL_DATABASE 存储所有相关爬取信息数据的数据库名

######该文件的作用主要是去声明几个变量

REDIS_HOST = 'localhost'

REDIS_PORT = 6379

REDIS_PASSWORD = None #redis 没有设定密码

REDIS_KEY = 'weixin'

PROXY_POOL_URL = 'http://127.0.0.1:5555/random'##web端的接口,用于获取代理

MYSQL_HOST = 'localhost'

MYSQL_PORT = 3306

MYSQL_USER = 'root'

MYSQL_PASSWORD = '123456'

MYSQL_DATABASE = 'weixin'#  Mysql 里面存储数据仓库名称

(2)request.py 用于设定请求链接的格式,即需要哪些参数

mport config
from config import *
from requests import Request




####这个的作用是,本项目是将请求的链接作为队列存储,那么不可能每一个链接的参数都设置一遍,这就需要调用requests里的Request
##这样,就可以为每个连接设定基本参数,下面参数包含了请求方式,请求的连接,解析连接用的回调函数(calllback),请求头,是否需要代理
##最长的容出时间,以及请求失败的次数,失败次数太多就不再请求
class WeixinRequest(Request):
    def __init__(self, url, callback, method='GET', headers=None, need_proxy=False, fail_time=0, timeout=TIMEOUT):
        Request.__init__(self, method, url, headers)
        self.callback = callback
        self.need_proxy = need_proxy
        self.fail_time = fail_time
        self.timeout = timeout

(3) db.py 当请求链接满足request.py 里面的设定,则将其加入redis数据库

########该文件的作用是是西安请求队列



from redis import StrictRedis#用于连接参数
import config
from config import *#s呼出存储的参数
from pickle import dumps,loads
"""
这两个的作用在崔庆才的书中有相关的注解,我们在request 里面存储了连链接的相关参数,生成了Request对象
但是在Redis 中不能直接存取Request对象,只能存取字符串,因此存取的时候需要序列化,取出的时候需要将其反序列化
"""
import request
from request import WeixinRequest


class redisque(object):
    def __init__(self):
        """
        连接本地redis
        """
        self.db=StrictRedis(host=REDIS_HOST,port=REDIS_PORT,password=REDIS_PASSWORD)


    def add(self,request):
        """

        :param request: 请求对象
        :return: 将对象添加到redis 队列
        """
        # 如果给定的request 满足WeixinRequest类的参数设定
        if isinstance(request,WeixinRequest):
            return self.db.rpush(REDIS_KEY,dumps(request)) #指定redis_key后,利用dump将其序列化然后传入redis队列
        return False


    def pop(self):
        """
        取出下一个Request 并反序列
        :return: Request or None
        """
        if self.db.llen(REDIS_KEY):#如果redis队列的长度不为0
            return loads(self.db.lpop(REDIS_KEY))#就从该指定的key中弹出一个请求链接,lpop 是先进先出,rpop 是先进后出
        else:
            return False


    def clear(self):
        self.db.delate(REDIS_KEY)


    def empty(self):
        return self.db.llen(REDIS_KEY)==0#不删除,只是清空




if __name__=='__main__':
    db=redisque()#首先实例化
    start_url='http://www.baidu.com'#传入一个起始的链接
    weixin_request=WeixinRequest(url=start_url,callback='hello',method='GET',need_proxy=True)#设定基础参数,回调函数开始就先写为hello,开头嘛,打个招呼
    db.add(weixin_request)
    request=db.pop()#取出一个链接
    print(request)
    print(request.callback,request.need_proxy)#顺便将该链接的回调值和请求代理打印出来

(4) mysql.py 用于将数据存入到数据库

import pymysql
import config
from config import *






class mysql(object):
    def __init__(self,host=MYSQL_HOST,username=MYSQL_USER,password=MYSQL_PASSWORD,port=MYSQL_PORT,database=MYSQL_DATABASE):#指定一些初始化变量,用户名,密码,端口,数据库名称
        try:
            self.db = pymysql.connect(host, username, password, database, charset='utf8', port=port)  # 表字段设定为utf-8
            self.cursor=self.db.cursor()##建立数据库的链接,设定游标
        except pymysql.MySQLError  as e:#报错
            print (e.args)


    def insert(self,table,data):
        """
        插入数据
        :param table:
        :param data:
        :return:
        """

        keys=",".join(data.keys())#data是字典的类型,这一步将所有的keys取出并且分割
        values=",".join(['%s']*len(data))#根据数据的长度来增加占位符的个数,返回的结果是[%s,%s,%s.........]
        sql_query='insert into %s (%s) valiues (%s)'%(table,keys,values)#查询语句


        try:
            self.cursor.excute(sql_query,tuple(data.values()))#游标移动插入相关的键值,以元组的形式掺入
            self.db.commit()
        except pymysql.MySQLError as e:
            print (e.args)
            self.db.rollback()

(5) weixinpaper.py 爬取的主程序

###爬虫代码的主要程序
import config
import db
import mysql
import request
import requests
import urllib
import pyquery
from pyquery import PyQuery as pq
from db import *
from mysql import *
from request import *
from config import *
from requests import Session#会话对象,当请求多个链接的时候,可以设置一次cookies后,运用到多个链接
from urllib.parse import urlencode
from requests import ReadTimeout,ConnectionError





class spider(object):
    base_url='http://weixin.sogou.com/weixin'
    keyword='python'
    headers = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
        'Accept-Encoding': 'gzip, deflate',
        'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2,mt;q=0.2',
        'Cache-Control': 'max-age=0',
        'Connection': 'keep-alive',
        'Cookie': 'IPLOC=CN1100; SUID=6FEDCF3C541C940A000000005968CF55; SUV=1500041046435211; ABTEST=0|1500041048|v1; SNUID=CEA85AE02A2F7E6EAFF9C1FE2ABEBE6F; weixinIndexVisited=1; JSESSIONID=aaar_m7LEIW-jg_gikPZv; ld=Wkllllllll2BzGMVlllllVOo8cUlllll5G@HbZllll9lllllRklll5@@@@@@@@@@; LSTMV=212%2C350; LCLKINT=4650; ppinf=5|1500042908|1501252508|dHJ1c3Q6MToxfGNsaWVudGlkOjQ6MjAxN3x1bmlxbmFtZTo1NDolRTUlQjQlOTQlRTUlQkElODYlRTYlODklOEQlRTQlQjglQTglRTklOUQlOTklRTglQTclODV8Y3J0OjEwOjE1MDAwNDI5MDh8cmVmbmljazo1NDolRTUlQjQlOTQlRTUlQkElODYlRTYlODklOEQlRTQlQjglQTglRTklOUQlOTklRTglQTclODV8dXNlcmlkOjQ0Om85dDJsdUJfZWVYOGRqSjRKN0xhNlBta0RJODRAd2VpeGluLnNvaHUuY29tfA; pprdig=ppyIobo4mP_ZElYXXmRTeo2q9iFgeoQ87PshihQfB2nvgsCz4FdOf-kirUuntLHKTQbgRuXdwQWT6qW-CY_ax5VDgDEdeZR7I2eIDprve43ou5ZvR0tDBlqrPNJvC0yGhQ2dZI3RqOQ3y1VialHsFnmTiHTv7TWxjliTSZJI_Bc; sgid=27-27790591-AVlo1pzPiad6EVQdGDbmwnvM; PHPSESSID=mkp3erf0uqe9ugjg8os7v1e957; SUIR=CEA85AE02A2F7E6EAFF9C1FE2ABEBE6F; sct=11; ppmdig=1500046378000000b7527c423df68abb627d67a0666fdcee; successCount=1|Fri, 14 Jul 2017 15:38:07 GMT',
        'Host': 'weixin.sogou.com',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
    }
    session=Session()#实例化会话
    queue=redisque()#实例化代理池队列
    mysql=mysql()#实例化存储对象
    
    """
    def __init__(self,base_url,keyword,headers,session,queue,mysql):
        self.base_url=base_url
        self.keyword=keyword
        self.headers=headers
        self.session=session
        self.queue=queue
        self.mysql=mysql
    """
        
        
    def get_proxy(self):#首先拿出一个代理
        """

        :return: 返回一个代理
        """
        try:
            response=requests.get(PROXY_POOL_URL)#请求config 中存储的一个web前端获取代理的接口,使用时必须打开代理池代码
            if response.status_code==200:
                print ('success to get proxy',response.text)
                return response.text
            return None
        except requests.ConnectionError as e:
            return e.args


    def  start(self):
        """

        :return: 构建请求链接,并将其加入到redis 队列里面
        """

        self.session.headers.update(self.headers)#全局的更新headers
        start_url=sef.base_url+'?'+urlencode({'query':self.keyword,'type':2})#https://weixin.sogou.com/weixin?type=2&query=python,请求连接的格式
        weixin_request=WeixinRequest(url=start_url,callback=self.parse_index,need_proxy=True)#说明两点,1已经设定了所有链接的全局headers,因此不需要再设定headers,另外,callback 指定解析的函数
        self.queue.add(weixin_request)#加入到redis 队列



    def parse_index(self,response):
        """

        :param response: 响应
        :return: 新的响应
        """
        doc=pq(response.text)#pyquery 解析界面
        items=doc('.news-box .news-list li .txt-box h3 a').items()#进入到主页,主页里面有很多文章,这一步是拿出所有文章所在链接的列表
        for item in items:
            url=item.attr('href')#获得每一篇文章的链接
            weixin_request=WeixinRequest(url=url,callback=self.parse_detail)#不必要添加代理,另外回调函数指给详情解析函数
            yield weixin_request#实际上最后返回的是解析详情页后的数据构成的字典

        next=doc('#sogou_next').attr('href')#锁定下一页取出对应的链接,值为请求参数
        if next:
            next_url=self.base_url+str(next)
            weixin_request=WeixinRequest(url=next_url,need_proxy=True,callback=self.parse_index)#回调自己
            yield weixin_request

    def parse_detail(self, response):
        """

        :param response: 响应
        :return: 返回微信文章详情页的信息
        """

        doc=pq(response.text)
        data={'title':doc('.rich_media_title').text(),'content':doc('.rich_media_content ').text(),'date':doc('.js_profile_qrcode #publish_time').text(),'nickname':doc('#meta_content .rich_media_meta rich_media_meta_text').text()}#以字典的形式存储文章内容的标题,内容,数据,微信昵称,微信号
        yield data#生成器



        """
        上述完成了主页的解析
        详情页的解析
        还差requests.get(weixin_request)环节
        因此需要一个请求函数,下面的方法是通过会话对象发送请求
        """

    def request(self,weixin_request):
        """

        :param weixin_request: 请求
        :return: 响应
        """
        try:
            if weixin_request.need_proxy:#参数类似self 的属性
                proxy=self.get_proxy()#根据方法从接口获得一个代理
                if proxy:#成功获取
                    proxise={'http://'+proxy,'https://'+proxy}
                    return self.session.send(weixin_request.prepare(),timeout=weixin_request.timeout, allow_redirects=False, proxies=proxies)#利用会话
            return self.session.send(weixin_request.prepare(), timeout=weixin_request.timeout, allow_redirects=False)
        except (ConnectionError, ReadTimeout) as e:
            print (e.args)




    def error(self,weixin_request):
        """

        :param weixin_request: 处理错误
        :return:
        """
        weixin_request.fail_time=weixin_request.fail_time+1#迭代
        print('Request Failed', weixin_request.fail_time, 'Times', weixin_request.url)
        if weixin_request.fail_time:#config里面设定的最大失败次数
            self.queue.add(weixin_request)#重新加入到调度队列中



    def schedule(self):#最为关键的调度队列
        """
        调度请求
        :return:
        """
        while not self.queue.empty():#当不为空的时候
            weixin_request=self.queue.pop()#取出一个链接
            callback=weixin_request.callback#看下面的run 代码,首先调用的是start函数,因此回调函数就是parse_index
            print ('schedule',weixin_request.url)
            response=self.request(weixin_request)#请求相应
            if response and response.status_code in VALID_STATUSES:
                results=list(callback(response))#利用回调函数指定解析函数
                if results:
                    for result in results:
                        print('New Result', type(result))
                        if isinstance(result,WeixinRequest):
                            self.queue.add(result)
                        if isinstance(result,dict):#字典类型说明是解析内容
                            self.mysql.insert('articles',result)
                else:
                    self.error(weixin_request)
            else:
                self.error(weixin_request)




    def run(self):
        self.start()
        self.schedule()





if __name__=='_-main__':
    spider=spider()
    spider.run()

通过此次实践学会了代理池的搭建,可以用于反爬,另外还学会了简单redis和mysql数据库的存储使用

上一篇下一篇

猜你喜欢

热点阅读