文档章节

scrapy的一些容易忽视的点(模拟登陆,传递item等)

o
 osc_a22drz29
发布于 2019/03/26 16:54
字数 2007
阅读 15
收藏 0

精选30+云产品,助力企业轻松上云!>>>

scrapy爬虫注意事项

<span id="1">一、item数据只有最后一条</span>

这种情况一般存在于对标签进行遍历时,将item对象放置在了for循环的外部。解决方式:将item放置在for循环里面。

def parse(self,response):
    #item = ExampleItem()  # 存在for循环时,item不要放置在这里
    for result in result_list:
        item = ExampleItem()  # 放置在for循环里面
        item['name'] = result.css('div a::text').extract_first()
        item['age'] = result.css('div #id').extract_first()
        yield item

<span id="2">二、item字段传递后错误,混乱</span>

有时候会遇到这样的情况,item传递几次之后,发现不同页面的数据被混乱的组合在了一起。这种情况一般存在于item的传递过程中,没有使用深拷贝。解决方式:使用深拷贝来传递item。

import copy

def parse_base(self,response):
    base_url = 'https://www.base_url.com'
    for result in result_list:
        item = ExampleItem()
        item['name'] = result.css('div a::text').extract_first()
        item['age'] = result.css('div #id').extract_first()
        yield scrapy.Request(url=base_url,meta=copy.deepcopy({'item':item}),callback=self.parse_detail) # 使用深拷贝将item存在meta中

def parse_detail(self,response):
    item = response.meta['item'] # 取出之前传递的item
    """
    do some thing
    """
    yield item

<span id="3">三、对一个页面要进行两种或多种不同的解析</span>

这种情况一般出现在对同一页面有不同的解析要求时,但默认情况下只能得到第一个parse的结果。产生这个结果的原因是scrapy默认对拥有相同的url,相同的body以及相同的请求方法视为一个请求。解决方式:设置参数dont_filter='True'。

def start_requests(self):
    base_url = 'https://www.base_url.com'
    yield scrapy.Request(url=base_url,dont_filter='True',callback=self.parse_one)
    yield scrapy.Request(url=base_url,dont_filter='True',callback=self.parse_two)

<span id="4">四、xpath中contains的使用</span>

这种情况一般出现在标签没有特定属性值但是文本中包含特定汉字的情况,当然也可以用来包含特定的属性值来使用(只不过有特定属性值的时候我也不会用contains了)。

<span>作者:村上春树</span>

<span>书名:挪威的森林</span>

以上面这两个标签为例(自行F12查看),两个span标签没有特定的属性值,但里面一个包含作者,一个包含书名,就可以考虑使用contains来进行提取。

def parse(self,response):
    item = BookItem()
    item['author'] = response.xpath('//span[contains(.//text(),"作者:")]//text()').split('作者:')[-1]  # 先用contains限定好特定的span标签,然后取出文本字符串并进行字符串切片得到需要的信息。下同
    item['book_name'] = response.xpath('//span[contains(.//text(),"书名:")]//text()').split('书名:')[-1]
    yield item

<span id="5">五、提取不在标签中的文本</span>

有时候会遇到这样的情况,文本在两个标签之间,但不属于这两个标签的任何一个。此时可以考虑使用xpath的contains和following共同协助完成任务。

示例:

<span>作者:</span>

"村上春树"

<span>书名</span>

"挪威的森林"

def parse(self,response):
    item = BookItem()
    item['author'] = response.xpath('//span[contains(.//text(),"作者")]/following::text()[1]')  # 先用contains限定好特定的span标签,然后提取出接下来的文本,并选择第一个。下同
    item['book_name'] = response.xpath('//span[contains(.//text(),"书名")]/following::text()[1]')
    yield item

<span id="6">六、使用css、xpath提取倒数第n个标签</span>

对于很多页面,标签的数量有时候无法保证是一致的。如果用正向的下标进行提取,很可能出现数组越界的情况。这种时候可以考虑反向提取,必要时加一些判断。

def parse(self,response)
	'''
	使用css和xpath分别提取信息
	'''
    item = ExampleItem()
    item['name'] = response.css('span::text').extract()[-1] # 使用css获取最后一个
    item['age'] = response.xpath('//span[last()-2]//text()').extract_first() # 使用xpath获取倒数第二个,类似的last()-3是倒数第三个
    yield item

<span id="7">七、提取表格信息</span>

其实对于信息抓取,很多时候我们需要对表格页面进行抓取。一般的方方正正的表格提取相对简单,这里不讨论。只说下含有合并单元格的情况。

这个网页的表格为例,定义5个字段批次,招生代码,专业,招生数量以及费用,注意到合并单元格的标签里有个rowspan属性,可以用来辨识出有几行被合并。我的思路是有多少行数据,就将batch批次扩展到多少个,形成一个新的列表,然后进行遍历提取数据

def parse(self, response):
    batch_list = response.css('tr[class="pc"] td::text').extract() # batch批次的列表,本来这里可以直接用extract_first()提取出来,这里我用extract()是为了让程序更通用化,也就是处理存在多个合并单元格的情况
    rowspan_list = response.css('tr[class="pc"] td::attr(rowspan)').extract() # rowspan值形成的列表,本例中只有一个值
    tr_list = response.css('tbody tr')[-2:] # 选择最后两个tr标签
    info_list = [] # 用来‘盛放’code、major、number、cost组合起来的列表
    for tr in tr_list:
        code = tr.css('td::text').extract_first()
        major = tr.css('td::text').extract()[1]
        number = tr.css('td::text').extract()[2]
        cost = tr.css('td::text').extract()[3]
        info_list.append([code,major,number,cost])
    batch_middle_list = list(map(lambda a, b: (int(a) - 1) * [b], rowspan_list, batch_list)) # python3 map要搭配list使用形成列表。将batch批次与number-1数量分别相乘得到一个新的列表(由列表组成的列表)
    batch_target_list = reduce(lambda a, b: a + b, batch_middle_list) # 使用reduce将列表的各项拼接成一个新的列表(由字符组成)。python3 reduce使用需要先导入,from functools import reduce
    for i in range(len(batch_target_list)):  # 此时patch批次新列表的元素个数与info_list的元素个数相同,可以进行遍历提取
        item = ExampleProjectItem()
        item['batch'] = batch_target_list[i]
        item['code'] = info_list[i][0]
        item['major'] = info_list[i][1]
        item['number'] = info_list[i][2]
        item['cost'] = info_list[i][3]
        yield item

<span id="8">八、模拟登陆</span>

当页面数据需要登陆进行抓取时,就需要模拟登陆了。常见的方式有:使用登陆后的cookie来抓取数据;发送表单数据进行登陆;使用自动化测试工具登陆,比如selenium配合chrome、firefox等,不过听说selenium不再更新,也可以使用chrome的无头模式。鉴于自动化测试的抓取效率比较低,而且我确实很久没使用过这个了。本次只讨论使用cookie和发送表单两种方式来模拟登陆。

  • 使用cookie

    使用cookie的方式比较简单,基本思路就是登陆后用抓包工具或者类似chrome的F12调试界面查看cookie值,发送请求时带上cookie值即可

base_url = 'http://www.example.com'
cookies_str = 'xxxxxxxx' # 登陆后的cookie字符串
cookies_dict = cookies_dict = {i.split('=')[0]: i.split('=')[1] for i in cookies_str.split('; ')} # 使用字典推导式将cookies_str转化为字典形式

def parse(self,response):
	yield scrapy.Request(url=base_url,cookies=self.cookies_dict,callback=self.parse_detail) # 默认使用get方式进行请求

def parse_detail(self,response):
    detail_url = 'http://www.xxx.com'
    yield scrapy.Request(url=detail_url,method='POST',cookies=self.cookies_dict,callback=self.parse_target)  # 每次发起请求带上cookie

def parse_target(self,response)
	target_url = 'http://www.ooo.com'
    yield scrapy.FormRequest(url=target_url,cookies=self.cookies_dict,callback=self.parse_final) # 使用FormRequest同样也是可以的
  • 发送表单方式进行登陆

    cookie是有有效期的,对于大量数据的抓取,更好的方式是发送表单进行模拟登陆。scrapy有专门的函数scrapy.FormRequest()用来处理表单提交。网上有些人说cookie没法保持,可以考虑用我下面的方式。

import copy

def start_requests(self):
    base_url = 'http://www.example.com'
    formdata = {
        'user':'1822233xxxx',
        'password':'test123'
    }
    yield scrapy.FormRequest(url=base_url,formdata=formdata,meta=copy.deepcopy{'cookiejar':1},callback=self.parse)  # 发送表单数据,注意将cookiejar放到meta中进行传送,也就是保持cookie。

def parse(self,response):
    yield scrapy.Request(url=detail_url,meta=copy.deepcopy({'cookiejar':response.meta['cookiejar']}),callback=self.parse_detail) # 每次发送请求都带上cookiejar,可以保持cookie
    
def parse_detail(self,response)
	print(response.text)  # 可以获取到登陆后页面的内容
o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。

暂无文章

土木转行Python的几个方向? - 知乎

零、背景 近一段时间有不少土木或兄弟专业的朋友加微信问我,自学Python一段时间后又出现了迷茫期,怎么破?不知道接下来走向哪里?下面,我把我知道的告诉你,至于Python之父是不是廖雪峰,...

osc_2ak6wwpl
1分钟前
0
0
如何选购便宜的SSL证书

我们在购物的时候经常会货比三家,而价格会占主导因素,有时候价格太高会让我们望而却步。而在选购SSL证书的时候也是同样的道理,市面上可供选择的SSL证书品牌和类型繁多,价格有高有低,那么...

安信证书
2分钟前
0
0
Spark SQL 中 Broadcast Join 一定比 Shuffle Join 快?那你就错了。

本资料来自 Workday 的软件开发工程师 Jianneng Li 在 Spark Summit North America 2020 的 《On Improving Broadcast Joins in Spark SQL》议题的分享。 文章目录 1 背景 2 TPC-H 测试 3 Br...

osc_k8v7r34l
2分钟前
0
0
空间直线与球面相交算法

目录 1. 原理推导 1.1. 直线公式 1.2. 求交 2. 具体实现 3. 参考 1. 原理推导 1.1. 直线公式 在严格的数学定义中,直线是无线延长,没有端点的线;射线是一端有端点,另外一段没有端点无线延...

osc_nfjwhlc1
3分钟前
0
0
七天用Go写个docker(第六天)

今天主要来实现一下 go-docker ps 的功能,也就是查看当前有哪些容器,简单说下思路,当我们启动一个容器时就为该容器创建一个文件夹用来保存该容器的一些信息,如果我们给容器指定了名字,那...

osc_zsaazovz
4分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部