使用scrapy框架爬取谷歌搜索结果的所有需要的表格

内容概要:爬取谷歌搜索结果“standard waveguid sizes”的全部网页链接,并在原码中找到需要的表格,再保存到本地。

1. 爬虫操作的基本原理

1.1. 爬虫需要完成的逻辑内容

爬虫从目标网页得到所需的信息需要经过以下三个步骤

  1. 从目标网页得到html源代码并下载到本地;
  2. 从源代码中找到自己需要部分的标签;
  3. 从标签中提取所需属性值或字符串。

1.2. html语言简单介绍

HTML称为超文本标记语言,是一种标识性的语言。它由一系列标签,以及各个标签的属性组成。Html语言最大的特点是标签可以层层嵌套,有一点类似于python中的父类与子类的关系。这样的形式有助于网页将各种各样的内容组织起来,用统一的界面进行展示。

1.3. 利用Python得到网页原码

Python获得网页原码的方法较多,这里介绍较为常用的两个包:urllib.request和requests。在使用这两种方法之前,需要得到目标网址的网页链接,即url。

1.3.1. urllib.request

urllib.request中的urlopen()方法可以向指定的url发送请求,并返回服务器响应的类文件对象,如:

response = urllib.request.urlopen("https://www.baidu.com")

服务器返回的类文件对象支持Python文件对象的操作方法,如read()方法读取文件全部内容,返回字符串:

html = response.read()

print(html)就可以看到目标网页的源代码。

1.3.2. requests

requests提供get()和post()两种方法向目标发送请求,其中get()方法执行效率较高,但由于请求数据放在url中,因此容易被捕获,不过对于安全性要求不高的请求,一般使用get()方法就可以了。如下:

Import requests
html = requests.get(‘https://www.baidu.com’)

get()和post()方法都返回一个类,其中包括一个属性为网页原码,以下代码即可查看:

print(html.text)

1.4. 从网页原码中提取所需信息

从网页原码中提取需要信息的方法有beautifulsoup、正则表达式、scrapy中的Xpath、selenium模拟浏览器操作等。但我在制作爬虫时没有尝试selenium的内容,这里不多做介绍。

1.4.1. 利用beautifulsoup提取所需信息

beautifulsoup可以实现方便的网页信息提取,通过将html原码包装成python对象、并且将标签属性作为对象中的值,再通过包装好的方法使得使用Python的人更容易理解,更加直观。beautifulsoup首先对html原码进行解析,得到beautifulsoup的对象soup:

from bs4 import BeautifulSoup 
soup = BeautifulSoup(html_doc, 'html.parser')

这其中第二个参数为使用的解析器,有以下四个选项:

以下为beautifulsoup的简单使用方法。
使用prettify()方法显示整理后的网页原码:

print(soup.prettify())

显示某子标签p的属性’class’:

print(soup.p['class'])

查找所有’a’标签

print(soup.find_all('a'))

1.4.2. 利用正则表达式提取所需信息

正则表达式是处理字符串的强大工具,它可以通过模糊匹配来确定字符串内是否存在所需内容,并对其进行提取,主要有match()方法、search()方法和findall()方法。

  1. match()方法
    match()方法从字符串头部开始匹配是否存在所需内容。如下,第二行代码将会返回SRE_Match对象,即一个关于匹配子串的包装类。通过group()方法可以显示提取出的字符串(第三行)。如果没有匹配,将会得到None(第四行)。
import re
match = re.match(r'www', 'www.baidu.com')
print(match.group())
re.match(r'baidu', 'www.baidu.com')
  1. search()方法
    search()方法可以查找或者匹配任意位置的字符串。同样,通过group()方法可以得到提取出的字符串。
search = re.search(r'baidu', 'www.baidu.com')
print(search.group())
  1. findall()方法
    findall()方法可以直接得到与指定规则相匹配的所有字符串,而不是SRE_Match对象。
print(re.findall(r'www','www.baidu.com,www.google.com')
  1. 正则表达式的模式
    以上方法中的第一个参数为正则表达式的模式,既可以为确定的字符串,也可以使用模糊匹配。以下为一些模糊匹配的实例


以下为具体案例:

string = "Cats are smarter than dogs"
result = re.match( r'(.*) are (.*?) .*', string)

其中,正则表达式为r’(.) are (.?) .’。第一个r表示在编译这一段字符串时不要将其中的一些符号进行转译,(.)表示可以匹配除了换行符之外的所有字符。(.*?)表示只匹配符合条件的字符串中的最少字符。.*效果与加括号一致,但不在最终结果中显示它匹配的字符。

print(result.group())
print(result.group(1))
print(result.group(2))

上述第一行显示完整的匹配结果。第二行显示(.)的结果,(.?)的结果在第三行中显示。

1.4.3. 利用scrapy中的Xpath提取所需信息

scrapy框架中返回的response类可以直接使用Xpath方法提取需要的信息,在查找所需标签及相关属性上具有明显优势。示例如下:

response.xpath(//table’)

上述方法可以得到网页原码中所有table标签及里面的内容。由于”//”表示多级定位,即使table不是原码中的第一级标签也可以被捕获。

response.xpath(//table/tbody/tr[@href=”https.//www.everythingf.com”])

上述方法表示查找table标签下的tbody子标签下,所有tr子标签中第一个属性href为”https.//www.everythingf.com”的标签。

response.xpath(//table/tbody/tr[@href=”https.//www.everythingf.com”]/text()).getall()

在上述代码中加入getall()方法可以提取所有满足条件的子标签,同时,//text()表示得到该tr标签下所有子标签的text。

1.4.4. 从网页中提取一个表格

一般html代码中,表格总是用标签table表示的,table中一般含有一个或两个标签,其中一个是tbody,代表表格内容;另一个命名各式各样,但都代表表头信息。对于一般的简单表格(如下图),表头也会被放在tbody中,因此只要爬取tbody就可以了。我们可以看到,tbody中全部都是名称为tr的标签,代表表格中的每一行;tr中为td标签,每一td存放一列。因此,最简单的爬取表格代码的流程为:

table = []
trs = find_all('tr')
for tr in trs:
   list = []
   for td in tr:
       list.append(td.string)
   table.append(list)

简单表格的html原码及样式举例来自(https://www.maigoo.com/news/463071.html)
对于“table”中含有表头标签的表格,由于表头中的元素占据的格数可能不为1,因此需要识别其中每个元素的“colspan”和“linespan”属性,分别代表其占据的列数和行数。由于python实现二维指针链表较为困难,而表格中元素长度不等,因此不能使用矩阵的方法进行组织,只能使用list,也给程序设计带来了一定麻烦。对于占多行的元素的情况,无法直接在判断“colspan”之后给下一行赋值,需要等到下一次循环的时候判断当前已经赋值的行数,在到达对应行数时将上一行的内容复制到下一行,因此程序设计为:

table_matrix = []
temp_occupy_infor = []#存储元素占据多列时的情况
present_column = 0#当前行中赋值到的列数
for sub_table in table:#有的表格可能有多个子标题,不止tbody
	for tr in sub_table:#每一行
	line = []#存储每一行元素
      	for td in tr:
          	flag_if_occupied,temp_occupy_infor = if_occupied(present_column,temp_occupy_infor)#判断当前位置是否为占据多列的元素
              	while flag_if_occupied:#给已经预定的位置
              		line.append(table_matrix[-1][present_column])
                	present_column += 1
                	flag_if_occupied,temp_occupy_infor = if_occupied(present_column,temp_occupy_infor)
#解决多个相邻元素都是占据多列的元素的问题
#判断不是多列元素后,赋值新元素
if ('colspan' in str(td)) and (type(td.get('colspan'))!=None):#判断新元素占几列
	td_column = int(td.get('colspan'))
	for i in range(td_column):#表格标题占多列的情况
		line.append(td.text)
		present_column += 1
else:
	line.append(td.text)       
	present_column += 1
#写入新的占位信息          
if 'linespan' in str(td):       
	td_line = int(td.get('linespan'))
	temp_occupy_infor.append([present_column-1,td_line-1])                                          #末尾有需要复制的值
	flag_if_occupied,temp_occupy_infor = self.if_occupied(present_column,temp_occupy_infor)  
	while flag_if_occupied:#给已经预定的位置赋值     
		line.append(table_matrix[-1][present_column])     
		present_column += 1     
		flag_if_occupied,temp_occupy_infor = if_occupied(present_column,temp_occupy_infor)                   
	table_matrix.append(line)  
	present_column = 0
save_contents(table_matrix)

1.2. 爬谷歌需要完成的一些设置

由于谷歌具有一些反爬虫机制,因此需要修改以下设置:

1.2.1. 设置user-agent

由于python直接访问服务器时user-agent为python -urllib/3.5,直接访问google会被拒绝,因此需要设置类似于浏览器的user-agent。同时,同一个user-agent频繁访问网页时会出现timeouterror(10060)的错误,网站拒绝被访问,因此需要设置一个user-agent库,并进行随机调取。

1.2.2. 设置proxy

代理ip可以在频繁访问网页时帮助网页规避封ip的风险,防止出现403的错误。一般情况下,在爬取大量数据时需要维护自己的ip库,但由于网上免费ip可用率较低,我在尝试十几个ip之后才找到一个可用的,由于时间比较紧,通过测试程序获得ip的方法也没有尝试,因此只使用了一个可用的代理ip。

1.2.3. 设置爬虫延时

设置延时可以保证爬虫以时间换数量,不会在获得较少数据时就被网站限制访问。同时,如果使用了异步框架,可以设置爬虫程序限制同时爬取网站的数量。

2. 利用scrapy框架完成google搜索结果中所需样本的提取

2.1. 介绍scrapy框架

scrapy框架是纯Python实现的专门用于各类爬虫的异步框架,由于各个模块分别运行,提升了系统的稳定性和爬取效率,同时由于使用者众多,资料易得,报错易改,因此成为此次爬取工作的不二之选。
scrapy框架介绍
上图是scrapy框架的整体架构,它将爬虫分为pipeline、spiders、scheduler、downloader以及engine五个模块,其中engine负责各部分数据传输和整体运行;scheduler负责存储将要爬取的网页,并依次向downloader提交请求;downloader负责向服务器发送访问请求,并获得相关网页的返回信息,包括网页原码等内容;spiders负责处理网页的返回信息,并将需要保存或做进一步处理的信息包装成元组发送给pipeline,同时将新发现的需要爬取的网页请求发送给scheduler;pipeline是所的数据的最后一道处理工序。

2.2. prase内容实现

prase是spiders的主要内容,我将来自google网页的原码处理放在prase中,可以向scheduler发送进一步爬取的请求。由于google的搜索结果页面十分干净,没有广告,因此来自google的原码可以通过beautifulsoup进行处理,只需要提取其中所有href属性,并滤掉google自身的网页即可。

2.3. pipeline内容实现

pipeline中实现的内容与上述得到表格的逻辑内容基本一致,但由于不同网页的开发者在网页设计的时候会加入个人习惯,添加许多看不见但存在于代码里的空标签和只有空格、tab等内容的标签。因此需要加入对tr、td、table等标签的审查,丢弃不需要的内容。

对于表格是否为含有标准波导尺寸的表格,根据已经获得的表格中各个表格的特点,发现非波导尺寸的表格有以下几类:

  1. 网站链接的广告表格,特点是表格中所有元素都含有链接;
  2. 描述具体波导器件的表格,特点是多行两列;
  3. 非标准表格,特点是table标签下的标签数量大于2;
    根据以上内容,可以采用以下三项得到较好的爬取结果:
  4. 网页链接数量小于表格元素数量的一半;
  5. 表格行数大于5,列数大于4;
  6. table标签的子标签数量小于等于2;

保存为csv文件时需要注意替换该格式文件不允许的字符,如’\xa0’。

2.4. Item内容实现

item中定义了从spiders传入pipeline的数据结构,即class item,定义如下:

class CrawlerGoogle0730Item(scrapy.Item): 
	google_page = scrapy.Field()    
	result_line = scrapy.Field()    
	body = scrapy.Field()    
	url = scrapy.Field()

2.5.middleware内容实现

中间件分为蜘蛛中间件和下载中间件,在下载中间件中我放入了user-agent的随机调取函数,实现每一次访问使用不同的user-agent:

import random
class RandomUserAgentMiddleware(object):#随机生成user_agent     
	def __init__(self, user_agent):        
		print("use RandomUserAgentMiddleware.__init__")        
		self.user_agent = user_agent     
	
	@classmethod    
	def from_crawler(cls, crawler):        
		print("use RandomUserAgentMiddleware.from_crawler")        
		return cls(user_agent=crawler.settings.get('MY_USER_AGENT'))     
		
	def process_request(self, request, spider):        
		agent = random.choice(self.user_agent)        	
		request.headers['User-Agent'] = agent

2.6. setting内容实现

setting中定义了user-agent库、中间件函数接口及优先级、网站访问延迟时间、同时访问数量等。

3.出现的问题及解决方法

3.1. 典型错误:timeouterror

造成timeouterror的原因有很多,在通过浏览器可以访问的情况下,绝大多数情况都是爬虫被对方发现而导致的主动延迟或不允许访问。而被对方发现的原因无非以上三种:没有设置proxy、user-agent中有python的信息或没有随机抽取、爬虫访问频率过高。通过1.5中介绍的内容可以进行规避。

3.2. 典型错误:无法找到网页

由于google下载的原码与开发者工具中看到的不符,因此从a标签的href属性获取的网页是无法直接访问的。通过观察下载的原码,可以看到a标签中href属性除了其本身的内容,还有ping属性中的一部分,因此可以采用正则表达式或Python的字符串搜索函数进行提取。

3.3. 典型错误:爬取的google原码中无法找到下一页的链接

这个问题还是由于google的原码与开发者工具中看到的不一致而产生的。在这个过程中,我尝试使用精度更高的beautifulsoup编译器、scrapy的Xpath方法、正则表达式等进行寻找,但都没有结果。经过多次尝试,我发现各页搜索结果的超链接可以直接以str_1+str(google_page*10)+str_2的形式进行表示,因此解决了这个问题。

3.4. 典型错误:yield 返回后报IndentationError: unexpected indent

这个错误是由scrapy库产生的。由于python本身对缩进要求较高,而prase是异步调用,因此可能会产生将prase()函数中的yield换作return就不报错、使用yield就报错的情况。在查阅国内论坛无果后,我在github的论坛中找到了解决办法:将prase()函数中的所有整行的注释全部删除,问题就解决了。
3.4.典型错误描述

完整代码请点击此链接

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值