python 爬虫二

内容回顾

- 模拟登陆:
- sometimes我们需要爬取基于当前用户的用户信息(需要登录后才可查看)
- 实现流程:
- 借助于抓包工具,抓取点击登录按钮发起的post请求(url,参数(动态参数))
- 携带cookie对其他子页面进行请求发送
- 反爬机制:
- robots
- UA检测
- 验证码
- cookie
- 禁ip
- 动态请求参数

在程序中 是否可以一味的使用多线程,多进程?

(推荐)使用单线程+多任务异步协程

event_loop:事件循环,相当于一个无限循环,我们可以把一些特殊函数注册(放置)到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
程序是按照设定的顺序从头执行到尾,运行的次数也是完全按照设定。当在编写异步程序时,必然其中有部分程序的运行耗时是比较久的,需要先让出当前程序的控制权,让其在背后运行,让另一部分的程序先运行起来。
当背后运行的程序完成后,也需要及时通知主程序已经完成任务可以进行下一步操作,但这个过程所需的时间是不确定的,需要主程序不断的监听状态,一旦收到了任务完成的消息,就开始进行下一步。loop就是这个持续不断的监视器。
coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到事件循环中,
它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,
而是返回一个协程对象。
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。
另外我们还需要了解 async/await 关键字,它是从 Python 3.6 才出现的,专门用于定义协程。其中,async 定义一个协程,await 用来挂起阻塞方法的执行。
了解携程概念基础后,我们来看看一个协程,具体需要什么?

事件循环-就是一个无限的循环
协程:特殊的函数 async修饰之后 这个函数调用不会立刻执行 会封转到协程对象,只有协程注册到任务对象中,并且任务对象注册事件循环中 才会执行
await:只要有阻塞操作 一定要在前面加await

协程基础

import asyncio
async def request(url):
    print('正在请求',url)
    print('下载成功',url)
c=request('www.baidu.com')

#实例化一个事件对象
loop=asyncio.get_event_loop()
#创建一个任务对象 将协程封装到该对象中 #task=loop.create_task(c) #第二种任务对象 task=asyncio.ensure_future(c) #将协程对象注册到事件循环对象中,并启动事件循环对象 loop=run_until_complete(task)

多任务异步协程

from time import sleep
import asyncio
import time
urls = ['www.baidu.com','www.sogou.com','www.goubanjia.com']
start = time.time()
async def request(url):
    print('正在请求:',url)
    #在多任务异步协程实现中,不可以出现不支持异步的相关代码。
    # sleep(2)
    await asyncio.sleep(2)
    print('下载成功:',url)

#实例化一个事件循环对象
loop = asyncio.get_event_loop()

#任务列表:放置多个任务对象
tasks = []
for url in urls:
    c = request(url)
    #实例化任务对象的方法
    task = asyncio.ensure_future(c)
    tasks.append(task)

#将协程对象注册到事件循环中,并且我们需要启动事件循环
loop.run_until_complete(asyncio.wait(tasks))

print(time.time()-start)

多任务异步协程应用到爬虫中

可以看到 每次访问 都需要2秒 一种是6秒

from flask import Flask
import time

app = Flask(__name__)


@app.route('/bobo')
def index_bobo():
    time.sleep(2)
    return 'Hello bobo'

@app.route('/jay')
def index_jay():
    time.sleep(2)
    return 'Hello jay'

@app.route('/tom')
def index_tom():
    time.sleep(2)
    return 'Hello tom'

if __name__ == '__main__':
    app.run(threaded=True)
#aiohttp:支持异步的一个基于网络请求的模块
# pip install aiohttp
import asyncio
import aiohttp
import time
#单线程+多任务异步协程
urls = [
    'http://127.0.0.1:5000/jay',
    'http://127.0.0.1:5000/bobo',
    'http://127.0.0.1:5000/tom'
]
#代理操作有变化:
#async with await s.get(url,proxy="http://ip:port") as response:
async def get_pageText(url):
    async with aiohttp.ClientSession() as s:#实例化一个请求对象
        async  with await s.get(url) as response:#get方法
            page_text=await response.text()#只要有阻塞操作就使用await 获取返回值
            #借助回调函数进行响应数据的解析操作
            return page_text
#封装回调函数用于数据解析
def parse(task):
    #1.获取响应数据
    page_text = task.result()
    print(page_text+',即将进行数据解析!!!')
    #解析操作写在该位置

start=time.time()
tasks=[]
for url in urls:
    c=get_pageText(url)#使用这个函数
    task=asyncio.ensure_future(c)#对协程的一种封装
    task.add_done_callback(parse) # 给任务对象绑定回调函数用于数据解析
    tasks.append(task)

loop=asyncio.get_event_loop()#创建一个事件对象
loop.run_until_complete(asyncio.wait(tasks))#将协程注册到循环中,并启用这个循环
print(time.time()-start)

 selenium

概念:是一个基于浏览器自动话的模块.

和爬虫之间的关联?

  帮我们便捷的爬取到页面中动态加载出来的数据

  实现模拟登陆

使用流程:

pip install selenium

下载对应的驱动程序:http://chromedriver.storage.googleapis.com/index.html

查看对应的版本:https://blog.csdn.net/huilan_same/article/details/51896672

selenium简单应用

from  selenium import webdriver
from lxml import etree
import  time
bro=webdriver.Chrome(executable_path='./chromedriver.exe')#导入驱动
#让浏览器对指定url发起访问
bro.get('http://125.35.6.84:81/xk/')
#获取浏览器当前打开页面源码(可见既可得)
page_text=bro.page_source
time.sleep(2)
tree=etree.HTML(page_text)
name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
print(name)
time.sleep(2)
bro.quit()
####################打开淘宝输入想搜索的值###########################
from selenium import webdriver
import time
bro=webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.taobao.com')
#节点定位find系列的方法
input_text=bro.find_element_by_id('q')#找到id为q的
#节点交互
input_text.send_keys('苹果')#在text中输入苹果
time.sleep(2)

动作链

from selenium import webdriver
#导入动作链对应的模块
from  selenium.webdriver import ActionChains
import time
bro=webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
#如果定位的节点是被包含在iframes节点之中的,则必须使用switch_to进行frame的切换
bro.switch_to.frame('iframeResult') #iframe

div_tag=bro.find_element_by_id('draggable')#获取id
#实例化一个动作链对象(需要将浏览器对象作为参数传递给该对象的构造方法)
action=ActionChains(bro)
#单击且长按
action.click_and_hold(div_tag)

for i in range(5):
    #让div向右移动
    action.move_by_offset(17,0).perform()#移动17*5 perform立刻执行动作链
    time.sleep(0.5)
time.sleep(2)
bro.quit()#退出

无头浏览器

from selenium import webdriver
from lxml import etree
import time

from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options)
#让浏览器对指定url发起访问
bro.get('http://125.35.6.84:81/xk/')

#获取浏览器当前打开页面的页面源码数据(可见即可得)
page_text = bro.page_source
time.sleep(2)
tree = etree.HTML(page_text)
name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
print(name)
time.sleep(3)
bro.quit()

selenium规避被检测

from selenium import webdriver
from lxml import etree
import time
#规避检查
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])

bro = webdriver.Chrome(executable_path='./chromedriver.exe',options=option)
#让浏览器对指定url发起访问
bro.get('http://125.35.6.84:81/xk/')

#获取浏览器当前打开页面的页面源码数据(可见即可得)
page_text =bro.page_source
time.sleep(2)
tree=etree.HTML(page_text)
name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0]
print(name)
time.sleep(5)
bro.quit()

模拟QQ空间

import request
from selenium import webdriver
import time

driver=webdriver.Chrome(executable_path='./chromedriver.exe')
driver.get('https://qzone.qq.com/')
# 在web 应用中经常会遇到frame 嵌套页面的应用,使用WebDriver 每次只能在一个页面上识别元素,对于frame 嵌套内的页面上的元素,直接定位是定位是定位不到的。这个时候就需要通过switch_to_frame()方法将当前定位的主体切换了frame 里。
driver.switch_to.frame('login_frame')
driver.find_element_by_id('switcher_plogin').click()
driver.find_element_by_id('u').send_keys('1820405927')  # 这里填写你的QQ号
driver.find_element_by_id('p').send_keys('xxxxxx')  # 这里填写你的QQ密码
driver.find_element_by_id('login_button').click()
time.sleep(2)
driver.close()

pyppeteer 

 类是于一个centos的selenium 使用谷歌测试版的浏览器

超级鹰自动登陆12306

pip install Pillow

chaojiying_api.py

import requests
from hashlib import md5


class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        password = password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id  # 898175
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,  # 898175
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
                          headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()

12306程序

from selenium import webdriver
import time
from selenium.webdriver import ActionChains
from PIL import Image#切图
from chaojiying_api import Chaojiying_Client
bro=webdriver.Chrome(executable_path=r'./chromedriver.exe')
bro.get('https://kyfw.12306.cn/otn/login/init')

time.sleep(2)
code_img_ele=bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
time.sleep(2)
#验证码图片的左上角的坐标
location=code_img_ele.location#x,y
print('验证码图片左上角的坐标',location)
size=code_img_ele.size#验证码图片的长和宽
print('验证码图片的长和宽size',size)
#验证码左上角和右下角这2个点的坐标
rangle=(
    int(location['x']),
    int(location['y']),
    int(location['x'] + size['width']),
    int(location['y'] + size['height'])

)
#截取当前浏览器打开的这张页面的图像
bro.save_screenshot('aa.png')

i=Image.open('./aa.png')#读取这个图片
#截取下来验证码图片名称
code_img_name='./code.png'
#crop就可以根据左上角 和右下角的坐标进行指定区域的截取
frame=i.crop(rangle)
frame.save(code_img_name)

import requests
chaojiying = Chaojiying_Client('xuebaohua', '6xbh', '898175')  # 用户中心>>软件ID 生成一个替换 96001
im=open('./code.png','rb').read()
result=chaojiying.PostPic(im,9004)#9004验证类型
print("***************", result)
result=result['pic_str']#取得坐标
all_list = [] #[[x1,y1],[x2,y2],[x3,y3]]
if '|' in result:  # 117,75|188,146|117,75
    list_1 = result.split('|')
    count_1 = len(list_1)
    for i in range(count_1):
        xy_list = []
        x = int(list_1[i].split(',')[0])   #[[x,y],[]]
        y = int(list_1[i].split(',')[1])
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
    print(all_list,'11111')#存储的形式 [[260, 62], [46, 139]] 11111
else:
    x = int(result.split(',')[0])   #[[x,y]]
    y = int(result.split(',')[1])
    xy_list = []
    xy_list.append(x)
    xy_list.append(y)
    all_list.append(xy_list)
print(all_list)#[[251, 76]]
code_img = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')

action = ActionChains(bro)#进行动作链
for l in all_list:
    x=l[0]
    y=l[1]                                       #拿到截图图片进行过便宜
    ActionChains(bro).move_to_element_with_offset(code_img, x, y).click().perform()

bro.find_element_by_id('username').send_keys('135022')
time.sleep(2)
bro.find_element_by_id('password').send_keys('166')
time.sleep(2)
bro.find_element_by_id('loginSub').click()


'''
验证码图片左上角的坐标 {'x': 278, 'y': 274}
验证码图片的长和宽size {'height': 190, 'width': 293}
*************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '3069614092051000215', 'pic_str': '251,76', 'md5': '93d0b89555d109be569a44c5a3b997fe'}
[[251, 76]]
'''
'''
#2个12306验证码的数据
验证码图片左上角的坐标 {'x': 278, 'y': 274}
验证码图片的长和宽size {'height': 190, 'width': 293}
*************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '9069614192051000217', 'pic_str': '260,62|46,139', 'md5': '818ec8e895f95dff73a86583890038b0'}
[[260, 62], [46, 139]] 11111
[[260, 62], [46, 139]]
'''

scrapy:爬虫框架 异步爬取,高性能的数据解析+持久化存储操作

框架:集成了很多功能且具有很强通用性的一个项目模版

如何学习框架:

  学习框架的功能模块的具体使用

环境的安装:

linux:

  pip install scrapy

windows:

      a. pip3 install wheel

      b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

      c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl#借助实现异步

      d. pip3 install pywin32

      e. pip3 install scrapy

scrapy:爬取糗事百科文字

使用流程

  创建一个工程:scrapy startproject firstBlood

  cd firstBlood

  创建爬虫文件: scrapy genspider first www.baiud.com

  执行工程:scrapy crawl first --nolog(可选)

命令行的:scrapy crawl qiutu -o qiutu.csv  执行保存(局限性比较强,只能是.csv)

基于管道:

命令行代码

# -*- coding: utf-8 -*-
import scrapy

#爬虫类
class FirstSpider(scrapy.Spider):
    #爬虫文件的名称
    name = 'qiutu'
    #允许的域名
    # allowed_domains = ['www.baiud.com']
    #起始的url列表 被进行自动的请求发送
    start_urls = ['https://www.qiushibaike.com/text/']

    #用来解析数据
    #持久化存储,只可以将parse方法的返回值存储到磁盘文件
    def parse(self, response):
        div_list=response.xpath('//*[@id="content-left"]/div')
        all_data=[]
        for div in div_list:
            # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            content=div.xpath('.//div[@class="content"]/span//text()').extract()
            content=''.join(content)#将列表转成字符串
            dic={
                'author':author,
                'content':content
            }
            all_data.append(dic)
        return  all_data

持久化存储代码

items.py

import scrapy


class FirstbloodItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()#万能的数据类型
    author = scrapy.Field()
    content = scrapy.Field()

first.py

from ..items import FirstbloodItem
#爬虫类
class FirstSpider(scrapy.Spider):
    #爬虫文件的名称
    name = 'qiutu'
    #允许的域名
    # allowed_domains = ['www.baiud.com']
    #起始的url列表 被进行自动的请求发送
    start_urls = ['https://www.qiushibaike.com/text/']
    def parse(self, response):
        div_list=response.xpath('//*[@id="content-left"]/div')
        all_data=[]
        for div in div_list:
            # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
            # print(author,'11111')
            content=div.xpath('.//div[@class="content"]/span//text()').extract()
            content=''.join(content)#将列表转成字符串
            #实例化一个item类型的对象
            item=FirstbloodItem()
            #使用中括号的形式访问item对象中的属性
            item['author'] = author
            item['content'] = content
            #将item内容提交到管道
            yield item

pipelines.py

import pymysql
from redis import Redis
class FirstbloodPipeline(object):
    fp = None
    def open_spider(self,spider):#修改父类方法
        print('开始爬虫.......')
        self.fb=open('./qiutu.txt','w',encoding='utf-8')
    def process_item(self, item, spider):
        author=item['author']#取属性赋值
        content = item['content']
        self.fb.write(author+':'+content+'
')
        return item #返回给了下一个即将被执行的管道类
    def close_spider(self,spider):
        print('结束爬虫!!!')
        self.fb.close()

class FirstbloodPipeline(object):
    conn=None
    cursor=None
    def open_spider(self,spider):
        self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456.com',db='qiutu')
        print(self.conn)
    def process_item(self,item,spider):
        self.cursor=self.conn.cursor()
        try:
            self.cursor.execute('insert into qiutu values("%s","%s")'%(item['author'],item['content']))
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()#回滚
        return item
    def close_spider(self,seider):
        self.cursor.close()
        self.conn.close()

scrapy:使用总结

创建工程 scrapy startproject proname
创建爬虫文件
    cd proname
    scrapy genspider spidername www.baidu.com
爬虫相关属性方法
    爬虫文件的名称:name
    起始的url列表:start_urls 存储的url会被scrapy进行自动的请求发送
    parse(reponse):用来解析start_urls列表中url对应的响应数据
    response.xpath() extrct()
数据持久化存储
-基于终端指定
  只可以将parse方法的返回值进行持久化存储
  scrapy crawl spidername -o ./file
-基础管道持久化存储的编码流程
  数据解析
  -在item类中声明相关的属性用于存储解析到数据
  -将解析到的数据存储封装到item类型对象中
  -将item对象提交给管道类
  -item会被管道类中的process_item方法的item参数进行接收
  -process_item方法中编写基于item持久化存储的操作
  -在配置文件中开启管道
管道细节处理:
  -管道文件中一个类对应的是什么?
    一个类表达式将解析到数据存储到某一个具体的平台中
  -process_item方法中的返回值表示什么含义
    return item就是说将item传递给下一个即将被执行的管道类
  -open_spider#开始 close_spider#结束


手动请求发送

yield scrapy.Request(url,callback):callback回调一个函数用于数据解析

爬取阳光网前5页数据

import scrapy
from sunLinePro.items import SunlineproItem

class SunSpider(scrapy.Spider):
    name = 'sun'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']

    #通用的url模板(不可以修改)
    url = 'http://wz.sun0769.com/index.php/question/questionType?type=4&page=%d'
    page = 1

    def parse(self, response):
        print('--------------------------page=',self.page)
        tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
        for tr in tr_list:
            title = tr.xpath('./td[2]/a[2]/text()').extract_first()
            status = tr.xpath('./td[3]/span/text()').extract_first()

            item = SunlineproItem()
            item['title'] = title
            item['status'] = status

            yield item
        if self.page < 5:
            #手动对指定的url进行请求发送
            count = self.page * 30
            new_url = format(self.url%count)
            self.page += 1
            # 手动对指定的url进行请求发送
            yield scrapy.Request(url=new_url,callback=self.parse)

post请求发送和cookie的处理

- post请求的发送:(麻烦少用,登陆用requests)
- 重写父类的start_requests(self)方法
- 在该方法内部只需要调用yield scrapy.FormRequest(url,callback,formdata)
- cookie处理:scrapy默认情况下会自动进行cookie处理 

#COOKIES_ENABLED = False 如果把注释取消就不会存储
import scrapy


class PostdemoSpider(scrapy.Spider):
    name = 'postDemo'
    # allowed_domains = ['www.xxx.com']
    #https://fanyi.baidu.com/sug
    start_urls = ['https://fanyi.baidu.com/sug']
    #父类方法,就是将start_urls中的列表元素进行get请求的发送
    # def start_requests(self):
    #     for url in self.start_urls:
    #         yield scrapy.Request(url=url,callback=self.parse)

    def start_requests(self):
        for url in self.start_urls:
            data = {
                'kw':'cat'
            }
            #post请求的手动发送使用的是FormRequest
            yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)

    def parse(self, response):
        print(response.text)

请求传参:

- 使用场景:如果使用scrapy爬取的数据没有在同一张页面中,则必须使用请求传参

        - 基于起始url进行数据解析(parse)
            - 解析数据
                - 电影的名称
                - 详情页的url
                - 对详情页的url发起手动请求(指定的回调函数parse_detail),进行请求传参(meta)
                    meta传递给parse_detail这个回调函数
                - 封装一个其他页码对应url的一个通用的URL模板
                - 在for循环外部,手动对其他页的url进行手动请求发送(需要指定回调函数==》parse)
            - 定义parse_detail回调方法,在其内部对电影的简介进行解析。解析完毕后,需要将解析到的电影名称
                和电影的简介封装到同一个item中。
                - 接收传递过来的item,并且将解析到的数据存储到item中,将item提交给管道

5大核心组键

spider对url进行请求对象封装 传给引擎,转发给调度器

调度器有 队列,过滤器2个部分,将过滤请求加入到队列中

调度器在把返回的请求通过引擎 给下载器

下载器在上互联网上进行下载,互联网response给下载器

下载器给引擎,引擎给splder,产生item再给引擎 再给管道

引擎作用:解析所有数据,事物触发

 =====请求传参抓前5页内容

# -*- coding: utf-8 -*-
import scrapy
from ..items import MovieproItem

class MovieSpider(scrapy.Spider):
    name = 'movie'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/frim/index1.html']

    #通用的url模板只适用于非第一页
    url = 'https://www.4567tv.tv/frim/index1-%d.html'
    page = 2

    #电影名称(首页),简介(详情页)
    def parse(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            name = li.xpath('./div/a/@title').extract_first()#电影名称
            detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first()#电影详情url
            item = MovieproItem()
            item['name'] = name

            #对详情页的url发起get请求
            #请求传参:meta参数对应的字典就可以传递给请求对象中指定好的回调函数
            yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})
        if self.page <= 5:
            new_url = format(self.url%self.page)#第二页
            self.page += 1
            yield scrapy.Request(url=new_url,callback=self.parse)#再次调page
    #解析详情页的页面数据
    def detail_parse(self,response):
        #回调函数内部通过response.meta就可以接收到请求传参传递过来的字典
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc
        yield item

ua伪装 代理ip

下载中间件:批量拦截所有的请求和相应啊

拦截请求: UA伪装 代理ip

 

from scrapy import signals
import random

#批量拦截所有的请求和响应
class MiddlewearproDownloaderMiddleware(object):
    #UA池
    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
    ]
    #代理池
    PROXY_http = [
        '153.180.102.104:80',
        '195.208.131.189:56055',
    ]
    PROXY_https = [
        '120.83.49.90:9000',
        '95.189.112.214:35508',
    ]

    #拦截正常请求:request就是该方法拦截到的请求,spider就是爬虫类实例化的一个对象
    def process_request(self, request, spider):
        print('this is process_request!!!')
        #UA伪装
        request.headers['User-Agent'] = random.choice(self.user_agent_list)
        return None

    #拦截所有的响应
    def process_response(self, request, response, spider):

        return response

    #拦截发生异常的请求对象
    def process_exception(self, request, exception, spider):
        print('this is process_exception!!!!')
        #代理ip的设定
        if request.url.split(':')[0] == 'http':
            request.meta['proxy'] = random.choice(self.PROXY_http)
        else:
            request.meta['proxy'] = random.choice(self.PROXY_https)

        #将修正后的请求对象重新进行请求发送
        return request

scrapy+selenium 进行相应篡改

(慢死 有时候还不对)个人评价

scrapy中应用selenium的编码流程:
    - 爬虫类中定义一个属性bro
    - 爬虫类中重写父类的一个方法closed,在该方法中关闭bro
    - 在中间件类的process_response中编写selenium自动化的相关操作

wangyi163.py

import scrapy

from ..items import WangyiItem
from selenium import webdriver

class Wangye163Spider(scrapy.Spider):
    name = 'wangye163'
    # allowed_domains = ['www.163.com']
    start_urls = ['https://news.163.com/']
    # 浏览器实例化的操作只会被执行一次
    bro = webdriver.Chrome(executable_path='D:老男孩老师笔记第六阶段爬虫pachong练习day06wangyiwangyispiderschromedriver.exe')

    urls = []  # 5大板块对应的url 发送给中间件

    def parse(self, response):
        li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        for index in [3, 4, 6, 7, 8]:
            li = li_list[index]  # 查找第几个位子
            new_url = li.xpath('./a/@href').extract_first()
            print(new_url,1111)
            self.urls.append(new_url)
            # 五大板块对应的url进行请求发送
            yield scrapy.Request(url=new_url, callback=self.parse_news)

    # 用来解析每一个板块对应的新闻数据(新闻标题)
    def parse_news(self, response):
        div_list = response.xpath('//div[@class="ndi_main"]/div')
        for div in div_list:
            try:
                title = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first()
            except:
                continue
            new_detail_url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first()
            # 实例化item对象将解析内容存储到item对象中
            item = WangyiItem()
            item['title'] = title
            # 对详情页的url手动请求发送以便回去新闻内容   content在另一个解析对象中,所以要进行传参
            yield scrapy.Request(url=new_detail_url, callback=self.parse_detail, meta={'item': item})

    def parse_detail(self, response):  # 解析方法
        item = response.meta['item']  # 这时候就可以拿到item对象了

        # 通过response解析出新闻内容
        content = response.xpath('//div[@id="endText"]/p//text()').extract()
        if not content:
            content = response.xpath('//div[@class="viewport"]/div[@class="overview"]/p/text()').extract_first()
        # else:
        #     content = response.xpath('//div[@id="endText"]//text()').extract()
        content = ''.join(content).strip()
        item['content'] = content
        yield item

    def closed(self, spider):  # 重写父类的关闭方案法
        print('爬虫系统结束')
        self.bro.quit()

中间件

from scrapy import signals
from scrapy.http import HtmlResponse
from time import sleep

class WangyiDownloaderMiddleware(object):
    def process_request(self, request, spider):
        return None
        #在5五大板块中的url 如果是拦击response
    def process_response(self, request, response, spider):
        if request.url in spider.urls:  # 爬虫类中spider对赢得urls属性
            # 就要对其对应的相应对象进行处理

            # 获取了在爬中类中定义好的浏览器对象
            bro = spider.bro
            bro.get(request.url)  # 获取携带了新闻数据页面源码数据
            '''
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            sleep(1)
            '''

            page_text = bro.page_source  # 实例化一个新的响应对象 age_text是符合要求的里面包含了动态数据
            new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
            #满足条件的response
            return new_response
        else:
            return response

    def process_exception(self, request, exception, spider):
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

 CrawlSpider

作用:就是用于进行全站数据的爬取
- CrawlSpider就是Spider的一个子类

- 如何新建一个基于CrawlSpider的爬虫文件
- scrapy genspider -t crawl xxx www.xxx.com

- LinkExtractor连接提取器:根据指定规则(正则)进行连接的提取

- Rule规则解析器:将链接提取器提取到的链接进行请求发送,然后对获取的页面数据进行指定规则(callback)的解析

- 一个链接提取器对应唯一一个规则解析器

dianyin

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import SunlineproItem,ContentItem

class DianyiSpider(CrawlSpider):
    name = 'dianyi'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']

    link= LinkExtractor(allow=r'type=4&page=d+')#提取页码连接

    link1 = LinkExtractor(allow=r'question/2019d+/d+.shtml')  # 提取详情页的链接

    rules = (
        Rule(link, callback='parse_item', follow=False),#等于true就会自动爬取所有页
        Rule(link1, callback='parse_detail'),
    )
    #解析出标题和网友名称
    def parse_item(self, response):
        tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
        for tr in tr_list:
            title = tr.xpath('./td[2]/a[2]/text()').extract_first()
            netFriend = tr.xpath('./td[4]/text()').extract_first()
            item = SunlineproItem()#没有办法进行传参请求 所以弄了2个item
            item['title'] = title
            item['netFriend'] = netFriend

            yield item
    #解析出新闻的内容
    def parse_detail(self,response):
        content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td/text()').extract()
        content = ''.join(content)
        print(content,3333333333)
        item = ContentItem()
        item['content'] = content

        yield item

items

import scrapy
class SunlineproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title=scrapy.Field()
    netFriend = scrapy.Field()

class ContentItem(scrapy.Item):
    # define the fields for your item here like:
    content = scrapy.Field()

pipelines

class SunlineproPipeline(object):
    def process_item(self, item, spider):
        #接收到的item究竟是什么类型的
        if item.__class__.__name__ =='SunlineproItem':#返回当前对象的类名
            print(item['title'],item['netFriend'])
        else:
            print(item['content'])#不是就取出详情
        return item

settings中打开相关 设置

分布式爬取

- 概念:可以将一组程序执行在多台机器上(分布式机群),使其进行数据的分布爬取。
- 原生的scrapy框架是否可以实现分布式?
- 不可以?
- 调度器不可以被分布式机群共享
- 管道不可以被分布式机群共享
- 借助scrapy-redis这个模块帮助scrapy实现分布式
- scrapy-redis作用:
- 可以提供可以被共享的管道和调度器
- pip install scrapy-redis

- 分布式的实现流程
- 导报:from scrapy_redis.spiders import RedisCrawlSpider
- 修改爬虫文件的代码:
- 将当前爬虫类的父类修改成RedisCrawlSpider
- 将start_urls删除
- 添加一个新属性redis_key = 'ts':可以被共享的调度器中的队列名称

- 设置管道:
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
# 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
}
- 设置调度器:
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True

- 指定redis服务器
REDIS_HOST = '192.168.12.154'
REDIS_PORT = 6379

- 配置redis:
修改Redis的配置文件:redis.windows.conf
- #bind 127.0.0.1
- protected-mode no
- 携带配置文件启动redis服务
- redis-server ./redis.windows.conf
- 启动redis客户端

- 执行工程:scrapy runspider xxx.py

- 手动将起始url扔入调度器的队列中(redis-cli):lpush ts www.xxx.com

- redis-cli: items:xxx

###############

scrapy startproject scrapyRedisPro

scrapy genspider -t crawl test www.x.com

========test.py======

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapyRedisPro.items import ScrapyredisproItem
from scrapy_redis.spiders import RedisCrawlSpider
from scrapy_redis.spiders import RedisSpider

class TestSpider(RedisCrawlSpider):
    name = 'test'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    redis_key = 'ts' #可以被共享的调度器中的队列名称
    rules = (
        Rule(LinkExtractor(allow=r'type=4&page=d+'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
        for tr in tr_list:
            title = tr.xpath('./td[2]/a[2]/text()').extract_first()
            netFriend = tr.xpath('./td[4]/text()').extract_first()
            item = ScrapyredisproItem()
            item['title'] = title
            item['net'] = netFriend

            yield item
            #提交的item必须保证提交到可以被共享的管道中

=====items=====

import scrapy


class ScrapyredisproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    net = scrapy.Field()

====settings====

ITEM_PIPELINES = {
   # 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400
}
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True

REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379

CONCURRENT_REQUESTS = 2#请求进程

D:老男孩老师笔记第六阶段爬虫pachong练习day07scrapyRedisProscrapyRedisProspiders>scrapy runspider test.py #开启

C:UsersAdministrator>redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> ^C
C:UsersAdministrator>redis-cli -h 127.0.0.1
127.0.0.1:6379> lpush ts http://wz.sun0769.com/index.php/question/questionType?type=4&page=

127.0.0.1:6379> lrange test:items 0 -1

url增量爬虫

- 概念:监测网上数据更新的情况。
- 数据指纹

判断网站详情页的url是否进行请求发送

########dianying.py#############

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import ScrapydianyingItem
from redis import Redis


class DianyingSpider(CrawlSpider):
    name = 'dianying'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.4567tv.tv/frim/index1.html']
    link = LinkExtractor(allow=r'/frim/index1-d+.html')
    rules = (
        Rule(link, callback='parse_item', follow=False),#只爬取前几页
    )
    conn = Redis(host='127.0.0.1', port=6379)

    # 解析电影的名称和详情页的url
    def parse_item(self, response):
        li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
        for li in li_list:
            title = li.xpath('./div/a/@title').extract_first()
            detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first()
            item = ScrapydianyingItem()
            item['title'] = title

            # 判断该详情页的url是否进行请求发送
            ex = self.conn.sadd('movie_detail_urls', detail_url)
            if ex == 1:  # 说明detail_url不存在于redis的set中
                print('已有最新数据更新,请爬......')
                yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
            else:
                print('暂无新数据的更新!!!')

    def parse_detail(self, response):
        item = response.meta['item']
        desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['desc'] = desc

        yield item

########items############

class ScrapydianyingItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title=scrapy.Field()
    desc=scrapy.Field()

###########pipelines##########

class ScrapydianyingPipeline(object):
    def process_item(self, item, spider):
        dic = {
            'title':item['title'],
            'desc':item['desc']
        }
        conn = spider.conn

        conn.lpush('movie_data',dic)
        return item

##客户端连接redis进行验证

127.0.0.1:6379> keys *
1) "movie_detail_urls"
2) "movie_data"

127.0.0.1:6379> llen movie_data
(integer) 144

127.0.0.1:6379> smembers movie_detail_urls

数据增量爬虫

- 需求:爬取糗事百科中的段子和作者数据。

#############qiubai.py#############

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis
import hashlib
from ..items import BaikeItem
class QiubaiSpider(CrawlSpider):
    name = 'qiubai'
    # allowed_domains = ['www.baid.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    rules = (
        Rule(LinkExtractor(allow=r'/text/page/d+/'), callback='parse_item', follow=False),
    )
    # 创建redis链接对象
    conn = Redis(host='127.0.0.1', port=6379)
    def parse_item(self, response):
        div_list = response.xpath('//div[@id="content-left"]/div')
        for div in div_list:
            item = BaikeItem()
            item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first()
            item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first()
            # 将解析到的数据值生成一个唯一的标识进行redis存储
            source = item['author'] + item['content']
            source_id = hashlib.sha256(source.encode()).hexdigest()
            # 将解析内容的唯一表示存储到redis的data_id中
            ex = self.conn.sadd('data_id', source_id)
            if ex == 1:
                print('该条数据没有爬取过,可以爬取......')
                yield item
            else:
                print('该条数据已经爬取过了,不需要再次爬取了!!!')

###########items.py###########

import scrapy


class BaikeItem(scrapy.Item):
    # define the fields for your item here like:
    author=scrapy.Field()
    content=scrapy.Field()

#########pipelines.py#############

class BaikePipeline(object):
    def process_item(self, item, spider):
        dic = {
            'title':item['author'],
            'desc':item['content']
        }
        conn = spider.conn

        conn.lpush('qiubaiData',dic)
        return item

D:老男孩老师笔记第六阶段爬虫pachong练习day07aike>scrapy crawl qiubai 执行

原文地址:https://www.cnblogs.com/zaizai1573/p/10951746.html