【scrapy】使用方法概要(三)(转)

本文详细介绍使用Scrapy抓取并验证代理IP的过程,包括定义Item、爬虫开发、执行Pipeline进行过滤及验证,最终将有效IP持久化。

请初学者作为参考,不建议高手看这个浪费时间】

 

前两篇大概讲述了scrapy的安装及工作流程。这篇文章主要以一个实例来介绍scrapy的开发流程,本想以教程自带的dirbot作为例子,但感觉大家应该最先都尝试过这个示例,应该都很熟悉,这里不赘述,所以,将用笔者自己第一个较为完整的抓取程序作为示例作为讲解。

 

首先,要大规模抓取一个网站的内容,必要的资源便是代理ip这一资源,如果不使用代理ip,又追求抓取的速度,很可能会被被抓网站发现行踪并封掉抓取机,所以抓取大量可用的代理ip便是我们第一个任务。

大概这个爬虫要实现以下三个功能:

1. 抓取代理ip,端口信息

2. 验证代理ip,判断其透明性

3. 将可用的代理ip持久化到文件中以供后续抓取程序使用

 

http://www.cnproxy.com/ 代理服务器网便是一个很好的代理ip的来源,简单看一下,共有12个页面,页面格式相同:

http://www.cnproxy.com/proxy1.html

http://www.cnproxy.com/proxy10.html

 

http://www.cnproxy.com/proxyedu1.html

http://www.cnproxy.com/proxyedu2.html

 

准备就绪,下面正式开始:

1. 定义item,根据需求,抓取的item最后应该包含如下信息才好用:

#前4个数据为页面可以直接获取的

ip地址

端口

协议类型

地理位置

#后三个数据为pipeline中后期得到的数据,很有用

代理类型

延迟

时间戳

所以定义代码如下:

复制代码
复制代码
 1 # Define here the models for your scraped items
 2 #
 3 # See documentation in:
 4 # http://doc.scrapy.org/topics/items.html
 5 
 6 from scrapy.item import Item, Field
 7 
 8 class ProxyItem(Item):
 9     address   = Field()
10     port      = Field()
11     protocol  = Field()
12     location  = Field()
13 
14     type      = Field() # 0: anonymity #1 nonanonymity
15     delay     = Field() # in second
16     timestamp = Field()
复制代码
复制代码

 

2. 定义爬虫

爬虫中做的主要工作就是设置初始化urls,即【种子】,【可不是那种种子~是这种种子~】

然后在默认的parse函数中使用xpath可以轻松的获得所需要的字段,比如

addresses = hxs.select('//tr[position()>1]/td[position()=1]').re('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')

可以获得ip信息的数组

locations = hxs.select('//tr[position()>1]/td[position()=4]').re('<td>(.*)<\/td>')

可以获得地理位置信息的数组

 

唯一有点麻烦的就是端口信息,网站站长可能想到数据会被抓取的可能,所以端口的输出使用了js输出,这确实增加了抓取的难度,scrapy和一般的爬虫是不具备浏览器的javascript解释器的,也不会执行这些js代码,所以,爬虫拿到的html代码中的端口号好没有被输出出来~

源码是这个样子

image

所以,一定在html的上部,会有这些‘r’的定义,不难发现

image

经过几次刷新,发现这些定义并不是动态的,所以就简单些,直接拿到代码中的+r+d+r+d信息,将+号替换为空,将r替换成8,d替换成0即可,所以可以声明下边这样的一个map

port_map = {'z':'3','m':'4','k':'2','l':'9','d':'0','b':'5','i':'7','w':'6','r':'8','c':'1','+':''}

具体的爬虫代码如下

复制代码
复制代码
 1 from scrapy.contrib.spiders import CrawlSpider, Rule
 2 from scrapy.selector import HtmlXPathSelector
 3 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
 4 from proxy.items import ProxyItem
 5 import re
 6 
 7 class ProxycrawlerSpider(CrawlSpider):
 8     name = 'cnproxy'
 9     allowed_domains = ['www.cnproxy.com']
10     indexes    = [1,2,3,4,5,6,7,8,9,10]
11     start_urls = []
12     for i in indexes:
13         url = 'http://www.cnproxy.com/proxy%s.html' % i
14         start_urls.append(url)
15     start_urls.append('http://www.cnproxy.com/proxyedu1.html')
16     start_urls.append('http://www.cnproxy.com/proxyedu2.html')
17 
18     def parse(self, response):
19         hxs = HtmlXPathSelector(response)
20         addresses = hxs.select('//tr[position()>1]/td[position()=1]').re('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
21         protocols = hxs.select('//tr[position()>1]/td[position()=2]').re('<td>(.*)<\/td>')
22         locations = hxs.select('//tr[position()>1]/td[position()=4]').re('<td>(.*)<\/td>')
23         ports_re  = re.compile('write\(":"(.*)\)')
24         raw_ports = ports_re.findall(response.body);
25         port_map = {'z':'3','m':'4','k':'2','l':'9','d':'0','b':'5','i':'7','w':'6','r':'8','c':'1','+':''}
26         ports     = []
27         for port in raw_ports:
28             tmp = port
29             for key  in port_map:
30                 tmp = tmp.replace(key, port_map[key]);
31             ports.append(tmp)
32         items = []
33         for i in range(len(addresses)):
34             item = ProxyItem()
35             item['address']  = addresses[i]
36             item['protocol'] = protocols[i]
37             item['location'] = locations[i]
38             item['port']     = ports[i]
39             items.append(item)
40         return items
41 ~
复制代码
复制代码

 

3. 执行pipeline,过滤并检查抓取到的代理ip,并将其持久化到文件中

一般性的校验过程我就不赘述了,直接介绍如果验证代理可用性及透明性的方法。

这需要另一个cgi程序的帮助。简单来说一个代理是否透明,就是在做中转的时候是否会将源ip放到请求包中并能够被被抓取方获取,如果能,就说明这个代理不是透明的,使用的时候就要多留意。

一般非透明代理ip会将源ip放到HTTP_X_FORWARDED_FOR字段中,为了更严谨些,另一个cgi脚本将服务器能获取到的所有跟ip有关的数据echo出来,php代码如下:

复制代码
复制代码
 1 <?php
 2 
 3     echo "PROXYDETECTATION</br>";
 4     echo "REMOTE_ADDR</br>";
 5     var_dump($_SERVER['REMOTE_ADDR']);
 6     echo "</br>";
 7     echo "env_REMOTE_ADDR</br>";
 8     var_dump(getenv('REMOTE_ADDR'));
 9     echo "</br>";
10     echo "env_HTTP_CLIENT_IP</br>";
11     var_dump(getenv('HTTP_CLIENT_IP'));
12     echo "</br>";
13     echo "HTTP_CLIENT_IP</br>";
14     var_dump($_SERVER['HTTP_CLIENT_IP']);
15     echo "</br>";
16     echo "HTTP_X_FORWARDED_FOR</br>";
17     var_dump($_SERVER['HTTP_X_FORWARDED_FOR']);
18     echo "</br>";
19     echo "HTTP_X_FORWARDED</br>";
20     var_dump($_SERVER['HTTP_X_FORWARDED']);
21     echo "</br>";
22     echo "HTTP_X_CLUSTER_CLIENT_IP</br>";
23     var_dump($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']);
24     echo "</br>";
25     echo "HTTP_FORWARDED_FOR</br>";
26     var_dump($_SERVER['HTTP_FORWARDED_FOR']);
27     echo "</br>";
28     echo "HTTP_FORWARDED</br>";
29     var_dump($_SERVER['HTTP_FORWARDED']);
30     echo "</br>";
31 
32 ?>
复制代码
复制代码

 

假设这个服务地址为http://xxx.xxx.xxx.xxx/apps/proxydetect.php

那么pipelines的代码如下

 

复制代码
复制代码
 1 # Define your item pipelines here
 2 # Don't forget to add your pipeline to the ITEM_PIPELINES setting
 3 # See: http://doc.scrapy.org/topics/item-pipeline.html
 4 from scrapy.exceptions import DropItem
 5 import re
 6 import urllib
 7 import urllib2
 8 import time
 9 import exceptions
10 import socket
11 class ProxyPipeline(object):
12     def process_item(self, item, spider):
13         port = item['port']
14         port_re = re.compile('\d{1,5}')
15         ports   = port_re.findall(port)
16         if len(ports) == 0:
17             raise DropItem("can not find port in %s" % item['port'])
18         else:
19             item['port'] = ports[0]
20         #profiling the proxy
21         #detect_service_url = 'http://xxx.xxx.xxx.xxx:pppp/apps/proxydetect.php'
22         detect_service_url = 'http://xxx.xxx.xxx.xxx/apps/proxydetect.php'
23         local_ip           = 'xxx.xxx.xxx.xxx'
24         proxy_  = str('http://%s:%s' % (str(item['address']), str(item['port'])))
25         proxies = {'http':proxy_}
26         begin_time = time.time()
27         timeout = 1
28         socket.setdefaulttimeout(timeout)
29         try:
30             data  = urllib.urlopen(detect_service_url, proxies=proxies).read()
31         except exceptions.IOError:
32             raise DropItem("curl download the proxy %s:%s is bad" % (item['address'],str(item['port'])))
33 
34         end_time   = time.time()
35         if '' == data.strip():
36             raise DropItem("data is null the proxy %s:%s is bad" % (item['address'],str(item['port'])))
37         if data.find('PROXYDETECTATION') == -1:
38             raise DropItem("wrong response the proxy %s:%s is bad" % (item['address'],str(item['port'])))
39         if data.find('PROXYDETECTATION') != -1:
40             if data.find(local_ip) == -1:
41                 item['type'] = 'anonymity'
42             else:
43                 item['type'] = 'nonanonymity'
44             item['delay'] = str(end_time - begin_time)
45         item['timestamp'] = time.strftime('%Y-%m-%d',time.localtime(time.time()))
46 
47         #record the item info
48         fp   = open('/home/xxx/services_runenv/crawlers/proxy/proxy/data/proxies.txt','a')
49         line = str(item['timestamp']) + '\t' + str(item['address']) + '\t' + str(item['port']) + '\t' + item['type'] + '\t' + str(item['delay']) + '\n'
50         fp.write(line)
51         fp.close()
52         return item
复制代码
复制代码

 

其中local_ip为抓取服务器本机的ip地址

4. 最后一个要说的就是setting.py配置文件,大家看具体代码吧

复制代码
复制代码
 1 # Scrapy settings for proxy project
 2 #
 3 # For simplicity, this file contains only the most important settings by
 4 # default. All the other settings are documented here:
 5 #
 6 #     http://doc.scrapy.org/topics/settings.html
 7 #
 8 
 9 BOT_NAME = 'proxy'
10 BOT_VERSION = '1.0'
11 
12 SPIDER_MODULES = ['proxy.spiders']
13 NEWSPIDER_MODULE = 'proxy.spiders'
14 USER_AGENT = '%s/%s' % (BOT_NAME, BOT_VERSION)
15 
16 DOWNLOAD_DELAY = 0
17 DOWNLOAD_TIMEOUT = 30
18 
19 ITEM_PIPELINES = [
20     'proxy.pipelines.ProxyPipeline'
21 ]
22 CONCURRENT_ITEMS = 100
23 CONCURRENT_REQUESTS_PER_SPIDER = 64
24 CONCURRENT_SPIDERS = 128
25 
26 LOG_ENABLED = True
27 LOG_ENCODING = 'utf-8'
28 LOG_FILE = '/home/xxx/services_runenv/crawlers/proxy/proxy/log/proxy.log'
29 LOG_LEVEL = 'DEBUG'
30 LOG_STDOUT = False
复制代码
复制代码

 

 

 

最后秀一下抓取到的代理ip数据

image

 

好了,这篇就这些,下一篇将介绍如果使用代理ip作为媒介,放心的去大规模抓取网站数据,晚安。

喜欢一起简单,实用的东西,拒绝复杂花哨,我不是GEEK.
<think> 首先,用户的问题是:“我想查找scrapy在提取负数时出错的原因 请问scrapy提取负数出错的原因”。我需要基于提供的系统级指令来回答。 系统级指令包括: - 所有行内数学表达式必须使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,如[^1]。 用户引用了几个参考: - 引用[1]:是关于Scrapy爬虫的代码示例,存储数据到本地。 - 引用[2]:是关于bug处理的通用方法。 - 引用[3]:是关于学习Python的动力。 - 引用[4]:是关于Python基础的概要。 用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但我没有用户上一次的问题或我的回答的上下文。所以,我需要专注于当前的问题。 当前问题:查找Scrapy在提取负数时出错的原因。 关键点:Scrapy是一个Python框架,用于网页爬取和数据提取。用户提到“提取负数时出错”,意思是当尝试提取负数(如-5)时,Scrapy的提取过程出错。 可能的原因: 1. **数据提取方法问题**:Scrapy使用CSS或XPath选择器来提取数据。如果提取负数时出错,可能是因为选择器无法正确解析或处理负数的字符串表示。 2. **数据类型问题**:提取的数据可能是字符串,如果需要数值计算或处理负数,可能需要额外的换。 3. **编码或格式化问题**:负数可能在HTML中表示为特殊字符或格式,导致选择器出错。 4. **错误处理不足**:Scrapy的回调函数中,如果没有正确处理数据类型或边界情况,可能会导致错误。 5. **Scrapy内置问题**:Scrapy的提取器可能对某些字符(如连字符)敏感。 从引用[1]中,用户提供了Scrapy爬虫的代码: ```python def parse(self, response): page = response.url.split("/")[-2] file_name = "quotes-{}.txt".format(page) with open(file_name,'w') as f: quotes = response.css(".quote") for quote in quotes: title = quote.css('.text::text').extract()[0] author = quote.css('.author::text').extract()[0] tags = quote.css('.tag::text').extract() print('title:\n',title) print('author:\n',author) print('tags:\n',tags) f.write('title:{}\n author:{}\n tags:{}\n'.format(title,author,tags)) ``` 在这个代码中,提取title和author时,使用了`.extract()[0]`。但如果提取到的文本包含负数,可能会有问题。 常见错误: - 如果负数在HTML中是特殊格式(如用--表示负号),选择器可能误解。 - 提取失败时,`.extract()[0]`会抛出IndexError如果没有匹配项。 - 数据类型:title和author是字符串,但如果内容是数字(如-5),可能需要换。 引用[2]提到bug处理:使用print()调试、检查文档、管理bug。 引用[4]提到Python数据类型:字符串处理、格式化输出。 所以,我需要构建回答: 1. **诊断原因**:解释为什么提取负数可能出错。 2. **具体建议**:基于引用提供调试和解决方法。 3. **添加引用**:自然地引用相关部分。 4. **使用中文**:整个回答都用中文。 5. **生成相关问题**:在末尾添加相关问题。 6. **格式**:遵循数学表达式规则,但这个问题可能不涉及数学,所以可能不需要LaTeX。用户的问题是文本处理,所以我应该避免不必要的数学格式。 可能的原因总结: - **选择器问题**:CSS选择器可能无法正确捕获包含负数的文本。 - **编码问题**:HTML中的负号(-)可能与特殊字符混淆。 - **提取方法**:`.extract()[0]`假设总是有匹配;如果没有匹配,会出错。 - **类型换**:如果提取后需要处理为数字,但字符串包含非数字字符。 回答结构: 1. 介绍问题:解释Scrapy提取负数时可能的错误。 2. 可能原因:列出常见原因。 3. 调试方法:基于引用[2],建议使用print调试。 4. 解决方案:如何修复代码。 5. 引用:自然地添加引用。 6. 结尾:生成相关问题。 生成相关问题: - 如何调试Scrapy的数据提取错误? - Scrapy如何处理特殊字符的提取? - 如何在Scrapy中安全地提取数值数据? 确保回答清晰、逐步解决。 </think> ### Scrapy提取负数时出错的原因分析 当使用Scrapy框架提取负数(如$-5$或$-3.14$)时,常见错误可能源于数据提取方法、数据类型换或HTML解析问题。下面我将基于您提供的引用内容,逐步分析可能的原因并提供解决方案。分析过程参考了Scrapy的文档和通用调试实践[^1][^2]。 --- ### 1. **CSS/XPath选择器问题(最常见原因)** Scrapy通过CSS或XPath选择器提取数据。如果负数在HTML中以特殊格式表示(如使用连字符"-"),选择器可能无法正确匹配文本: - **示例情况**:在HTML中,负数可能被包裹在特殊标签中(如`<span class="negative">-5</span>`),但您的选择器(如`.text::text`)忽略了嵌套结构。 - **错误表现**:`.extract()[0]`可能返回空列表或截断字符串,导致`IndexError`或提取结果缺失负号(例如输出"5"而非"-5")。 - **调试建议**:在提取代码中添加`print()`语句分段检查数据完整性[^2],如在引用[1]的代码中添加: ```python title = quote.css('.text::text').extract()[0] print("Raw title extract:", title) # 检查是否包含负号 ``` - **解决方案**:优化选择器路径,确保覆盖负号的上下文。例如: - 使用完整XPath:`response.xpath('//span[@class="negative"]/text()').extract_first()`。 - 或添加伪类处理:`.css('.text::text, .negative::text').extract_first()`。 ### 2. **数据类型换错误** 提取的数据默认为字符串类型,直接处理负数时可能引发类型错误: - **问题根源**:如果您将提取的字符串(如`"-5"`)直接用于数值计算或存储,但未显式换为数字类型(如`int`或`float`),可能导致格式错误。例如,引用[1]中的`f.write()`方法可能错误写入字符串而非数值。 - **数学表达式示例**:假设需要计算 $x - y$,但$x$和$y$均为字符串,则表达式会失败。 - **错误表现**:Python抛出`ValueError`(如无法将字符串为浮点数)或输出格式混乱(如`tags:['-5']`而非数值)。 - **调试建议**:使用`type()`函数验证数据类型[^4]: ```python raw_num = quote.css('.number::text').extract_first() print("Type of extracted data:", type(raw_num)) # 输出应为<class 'str'> ``` - **解决方案**:在写入或计算前换数据类型: ```python try: numeric_value = float(raw_num) # 换为浮点数 except ValueError: print(f"Conversion error for: {raw_num}") # 处理无效格式 ``` ### 3. **HTML编码或特殊字符冲突** 负数中的负号("-")可能与HTML实体(如`−`)混淆,或受页面编码影响: - **问题根源**:某些网站使用HTML实体表示负号(如`−`),但Scrapy的默认解析器可能将其视为普通文本,导致提取结果不完整。 - **错误表现**:提取的文本显示乱码(如"'"5")或不包含负号。 - **调试建议**:检查响应内容的原始HTML(在Scrapy Shell中运行`view(response)`),确认负号的编码格式[^2]。 - **解决方案**: - 使用Scrapy的`replace_entities`参数处理实体:`.css('.text::text').re(r'[-\d.]+')`。 - 或在下载中间件中添加编码处理:设置`FEED_EXPORT_ENCODING = 'utf-8'`。 ### 4. **边界情况处理不足** 如果负数出现在动态内容或异常结构中,选择器可能失效: - **问题根源**:引用[1]的代码假设`.extract()[0]`总是有效,但当页面结构中无匹配元素时(如负数标签缺失),会触发`IndexError`。 - **错误表现**:爬虫崩溃并输出日志错误(如`list index out of range`)。 - **解决方案**:使用`.extract_first(default='')`替代`.extract()[0]`,提供默认值: ```python title = quote.css('.text::text').extract_first("Default Value") ``` - 这避免了空列表导致的异常,并允许后续处理。 ### 通用调试步骤(基于引用[2]) 1. **分段打印(Print Debugging)**:在关键步骤插入`print()`,检查提取前后数据的变化。例如: ```python quotes = response.css(".quote") print("Number of quotes found:", len(quotes)) # 确认匹配元素数量 ``` 2. **文档查阅**:参考Scrapy官方文档的[选择器部分](https://docs.scrapy.org/en/latest/topics/selectors.html),验证语法是否正确。 3. **错误归类**:记录错误日志并归类(如使用Teambin工具),区分选择器错误或类型换问题[^2]。 4. **本地测试**:在Scrapy Shell中直接测试提取逻辑: ```bash scrapy shell "http://example.com" >>> response.css('.negative::text').get() ``` 通过以上方法,多数负数提取错误可解决。核心原则是:**确保选择器覆盖负号上下文、显式换数据类型,并添加健壮的错误处理**。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值