本章主要是动手实践操作爬取微信文章并存储到数据库,学习到的东西如下:
- 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数据库的存储使用
网友评论