RUN__IT # scrapy中间件

本文详细介绍了Scrapy中间件的分类、作用及使用方法,包括下载中间件和爬虫中间件,重点讲解了如何实现随机User-Agent和代理IP的下载中间件,以及如何在Scrapy项目中配置和启用这些中间件。

scrapy中间件

1.1 scrapy中间件的分类

根据scrapy运行流程中所在位置不同分为:

下载中间件
爬虫中间件

1.2 scrapy中间的作用

主要功能是在爬虫运行过程中进行一些处理,如对非200响应的重试(重新构造Request对象yield给引擎)
也可以对header以及cookie进行更换和处理
其他根据业务需求实现响应的功能
但在scrapy默认的情况下 两种中间件都在middlewares.py一个文件中

爬虫中间件使用方法和下载中间件相同,常用下载中间件

2 下载中间件的使用方法:

接下来我们对腾讯招聘爬虫进行修改完善,通过下载中间件来学习如何使用中间件 编写一个Downloader Middlewares和我们编写一个pipeline一样,定义一个类,然后在setting中开启

Downloader Middlewares默认的方法:

process_request(self, request, spider):

当每个request通过下载中间件时,该方法被调用。
返回None值:继续请求
返回Response对象:不再请求,把response返回给引擎
返回Request对象:把request对象交给调度器进行后续的请求
process_response(self, request, response, spider):

当下载器完成http请求,传递响应给引擎的时候调用
返回Resposne:交给process_response来处理
返回Request对象:交给调取器继续请求

3. 定义实现随机User-Agent的下载中间件

3.1 在middlewares.py中完善代码
import random
from Tencent.settings import USER_AGENTS_LIST # 注意导入路径,请忽视pycharm的错误提示

class UserAgentMiddleware(object):
    def process_request(self, request, spider):
        user_agent = random.choice(USER_AGENTS_LIST)
        request.headers['User-Agent'] = user_agent
       
3.2 在爬虫文件tencent.py的每个解析函数中添加
class CheckUA:
    def process_response(self,request,response,spider):
        print(request.headers['User-Agent'])
3.3 在settings中设置开启自定义的下载中间件,设置方法同管道
DOWNLOADER_MIDDLEWARES = {
   'Tencent.middlewares.UserAgentMiddleware': 543,
}
3.4 在settings中添加UA的列表
USER_AGENTS_LIST = [ 
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", \
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", \
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", \
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", \
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", \
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", \
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", \
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5" ]
3.5 运行爬虫观察现象

4 代理ip的使用

4.1 思路分析

代理添加的位置:request.meta中增加proxy字段
获取一个代理ip,赋值给request.meta[‘proxy’]
代理池中随机选择代理ip
代理ip的webapi发送请求获取一个代理ip

4.2 具体实现
class ProxyMiddleware(object):
    def process_request(self,request,spider):
        proxy = random.choice(proxies) # proxies可以在settings.py中,也可以来源于代理ip的webapi
        # proxy = 'http://192.168.1.1:8118'
        request.meta['proxy'] = proxy
        return None # 可以不写return
4.3 检测代理ip是否可用

在使用了代理ip的情况下可以在下载中间件的process_response()方法中处理代理ip的使用情况,如果该代理ip不能使用可以替换其他代理ip

class ProxyMiddleware(object):
    def process_response(self, request, response, spider):
        if response.status != '200' and response.status != '302':
            #此时对代理ip进行操作,比如删除
            return request

总结
中间件的使用:

完善中间件代码:

process_request(self, request, spider):

当每个request通过下载中间件时,该方法被调用。
返回None值:继续请求
返回Response对象:不再请求,把response返回给引擎
返回Request对象:把request对象交给调度器进行后续的请求
process_response(self, request, response, spider):

当下载器完成http请求,传递响应给引擎的时候调用
返回Resposne:交给process_response来处理
返回Request对象:交给调取器继续请求
需要在settings.py中开启中间件 DOWNLOADER_MIDDLEWARES = { ‘myspider.middlewares.UserAgentMiddleware’: 543, }

from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import ElementClickInterceptedException import csv import pymysql import os import requests import time import datetime import re def download_image(url, save_path, filename): if not url or url.startswith((&#39;data:image&#39;, &#39;javascript:&#39;)): print(f"无效的图片URL: {url}") return # 补全 URL(如果以 // 开头) if url.startswith(&#39;//&#39;): url = &#39;https:&#39; + url # 当当网图片通常支持 HTTPS elif not url.startswith((&#39;http://&#39;, &#39;https://&#39;)): url = &#39;https://&#39; + url # 其他情况默认加 HTTPS # 清洗文件名 def sanitize_filename(name): return re.sub(r&#39;[\\/*?:"<>|]&#39;, "_", name) safe_filename = sanitize_filename(filename) save_to = os.path.join(save_path, safe_filename) # 请求配置 headers = { &#39;User-Agent&#39;: &#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36&#39;, &#39;Referer&#39;: &#39;http://www.dangdang.com/&#39; } # 重试机制 max_retries = 3 for attempt in range(max_retries): try: response = requests.get(url, headers=headers, timeout=10) if response.status_code == 200: if not os.path.exists(save_path): os.makedirs(save_path) with open(save_to, &#39;wb&#39;) as f: f.write(response.content) print(f&#39;图片已保存: {save_to}&#39;) break else: print(f"HTTP错误: {response.status_code} (URL: {url})") except Exception as e: print(f"第 {attempt + 1} 次尝试失败: {str(e)}") time.sleep(2) else: print(f"下载失败(超过最大重试次数): {url}") # 初始化浏览器 driver_path = r"D:\chromedriver-win32\chromedriver.exe" options = webdriver.ChromeOptions() options.add_argument(&#39;--headless&#39;) service = Service(driver_path) driver = webdriver.Chrome(service=service, options=options) try: driver.get(&#39;http://www.dangdang.com/&#39;) # 设置窗口大小,可能避免某些元素遮挡 driver.set_window_size(1920, 1080) # 使用更健壮的方式获取搜索框 search_term = input("请输入要搜索的图书类型:") search_box = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "key_S")) ) search_box.clear() search_box.send_keys(search_term) # 等待搜索按钮可点击 search_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, ".search .button")) ) # 尝试处理可能遮挡的浮动元素 try: WebDriverWait(driver, 5).until( EC.invisibility_of_element_located((By.CSS_SELECTOR, "a.robot")) ) except: pass # 尝试点击搜索按钮 try: search_btn.click() except ElementClickInterceptedException: print("常规点击被拦截,尝试使用JavaScript点击") driver.execute_script("arguments[0].click();", search_btn) id = 0 data = [[&#39;图书编号&#39;, &#39;图书名称&#39;, &#39;图书作者&#39;, &#39;图书出版社&#39;, &#39;图书图片url&#39;, &#39;图书价格&#39;, &#39;图书简介&#39;]] # 使用更可靠的翻页方式 for _ in range(3): # 限制爬取3页防止无限循环 # 等待结果加载 WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.CSS_SELECTOR, "#search_nature_rg")) ) book_items = driver.find_elements(By.CSS_SELECTOR, "#search_nature_rg li") for item in book_items: books = [] id += 1 books.append(id) # 标题 try: title = item.find_element(By.CSS_SELECTOR, "a[dd_name=&#39;单品标题&#39;]").get_attribute("title") if not title: title = item.find_element(By.CSS_SELECTOR, "a:first-child").text.strip() books.append(title) except: books.append("未知标题") # 作者 try: author = item.find_element(By.CSS_SELECTOR, ".search_book_author a").text books.append(author) except: books.append("未知作者") # 出版社 try: press_span = item.find_element(By.CSS_SELECTOR, ".search_book_author span:nth-child(3)") press = press_span.find_element(By.TAG_NAME, "a").text books.append(press) except: books.append("未知出版社") # 图片URL try: img = item.find_element(By.CSS_SELECTOR, "a img") src = img.get_attribute("src") if "url_none.png" in src: src = img.get_attribute("data-original") or "" books.append(src) except: books.append("") # 价格 try: price = item.find_element(By.CSS_SELECTOR, ".search_now_price").text.replace("¥", "") books.append(price) except: books.append("0.00") # 简介 try: intro = item.find_element(By.CSS_SELECTOR, ".detail").text books.append(intro) except: books.append("无简介") data.append(books) # 翻页 try: next_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, ".next")) ) next_btn.click() time.sleep(2) except: print("已到达最后一页或翻页失败") break # 保存到CSV timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") csv_filename = f&#39;dangdang_books_{timestamp}.csv&#39; with open(csv_filename, &#39;w&#39;, newline=&#39;&#39;, encoding=&#39;utf-8-sig&#39;) as f: writer = csv.writer(f) writer.writerows(data) # 数据库操作 conn = pymysql.connect( user="root", password="123456", host="localhost", port=3306, charset=&#39;utf8mb4&#39; ) cursor = conn.cursor() try: cursor.execute("CREATE DATABASE IF NOT EXISTS xyw CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci") except Exception as e: print(f"创建数据库失败: {str(e)}") exit() # 选择数据库 conn.select_db("xyw") # 创建表(如果不存在) create_table_sql = """ CREATE TABLE IF NOT EXISTS dangdang( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) CHARACTER SET utf8mb4, author VARCHAR(100) CHARACTER SET utf8mb4, press VARCHAR(100) CHARACTER SET utf8mb4, src VARCHAR(255), price DECIMAL(10,2), introduction TEXT CHARACTER SET utf8mb4 ); """ cursor.execute(create_table_sql) # 插入数据 insert_sql = """ INSERT INTO dangdang (title, author, press, src, price, introduction) VALUES (%s, %s, %s, %s, %s, %s) """ # 新增:统计成功插入的记录数 inserted_count = 0 for row in data[1:]: try: price_value = float(row[5]) if row[5].replace(&#39;.&#39;, &#39;&#39;, 1).isdigit() else 0.0 cursor.execute(insert_sql, (row[1], row[2], row[3], row[4], price_value, row[6])) inserted_count += 1 # 每成功插入一条,计数器加1 except Exception as e: print(f"插入数据失败: {str(e)}") conn.commit() print(f"成功插入 {inserted_count} 条数据到数据库") # 使用自定义计数器 # 下载图片(修正缩进) save_directory = &#39;download&#39; if not os.path.exists(save_directory): os.makedirs(save_directory) for i, row in enumerate(data[1:], 1): image_url = row[4] if image_url: new_filename = f&#39;{str(i).zfill(4)}.jpg&#39; download_image(image_url, save_directory, new_filename) finally: # 确保关闭资源 if &#39;conn&#39; in locals() and conn.open: cursor.close() conn.close() driver.quit()将这个代码改为selenium+scrapy,功能不要改变xyw/ ├── scrapy.cfg # Scrapy 项目部署配置文件(定义项目名称、部署目标等) └── xyw/ # 项目核心模块目录(与项目名同名) ├── __init__.py # Python 包标识文件(空文件或做包初始化) ├── items.py # 定义爬取数据的结构化字段(类似数据模型) ├── middlewares.py # 中间件文件(处理请求、响应的自定义逻辑,如 UA 伪装、代理等) ├── pipelines.py # 管道文件(数据清洗、存储逻辑,如存数据库、去重等) ├── run.py # 自定义运行入口(可能用于启动爬虫,替代 `scrapy crawl` 命令) ├── settings.py # 项目全局配置(爬虫开关、并发数、日志、管道启用等) ├── show.py # 自定义脚本(可能用于展示爬取结果、调试数据等,非 Scrapy 标准文件) └── spiders/ # 爬虫脚本目录 ├── __init__.py # Python 包标识文件(空文件或做爬虫包初始化) └── dangdang_spider.py # 当当网爬虫脚本(具体的爬取逻辑实现) 这是项目框架,每个文件的内容是什么
最新发布
06-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值