python爬虫Pragmatic系列IV

python爬虫Pragmatic系列IV

说明:

在上一篇博客中,我们已经做到了从赶集网上单个首页中抓取所有的链接,并下载下来,分析后存入Excel中。

本次目标:

在本节中,我们将使用python多线程技术从赶集网上抓取链接并分析,注意,我们这次能够抓获的链接数目可以远远大于上一篇博客中抓获的。

分析:

用爬虫统计信息那自然数据越多越好,为了获取更多的数据,我们先研究下如何打开上千个赶集网上公司链接。

打开首页(http://bj.ganji.com/danbaobaoxian/o1/),在页面底部能够看到一排分页,如下图:

简单分析可以发现其分页链接请求是由A+B形式组成的,A为(http://bj.ganji.com/danbaobaoxian/),而B为(oi),其中i为数字。经过验证后发现,i的范围为:[1,300+)。由此,我们就可以利用以上的链接去访问各个首页并获得各个首页中包含的公司页面链接。但是问题来了,一个首页上公司共有九十多家,假设我们抓取十个主页面上公司的链接,每个公司从下载到分析到写入Excel假设需要0.2s,那么共需要180s(=0.2*10*90)。而且当网速差的时候,所需要的时间会更长。由此,我们需要多线程来处理该问题。

学习python多线程可以看这里:w3cshoolPython多线程

为了满足这次爬虫的需要,我在原来代码的基础上做了以下几个改动。

  • 多线程

使用多线程,每个线程处理每个界面上的公司链接的下载和信息的提取写入,这样并发的处理能够使程序的效率更高而且能够抓取更多的信息。

  • 爬虫类

在之前的博客中,我们都是单独的使用下载类和分析类分别进行操作,需要先运行下载类,然后在运行分析类。我们发现其实这两个操作其实都可以抽象成赶集网上抓取信息的子功能,并且,我们也希望这两者能够通过一个程序运行,这样也减少了操作的复杂性。

于是,我们构建一个赶集网爬虫类,将下载和分析功能聚合在一起,并且,为了适应多线程,我们让该类继承threading.Thread类,重写重写__init__()和__run__()函数,使其能够满足我们并发下载的需要。

  • 代码的复用

在设计爬虫类时,我们发现原先代码中很多函数并不适合直接拿过来粘贴使用,其复用性较差,于是我们需要重构几个函数。

对于下载而言,我们之前的使用方法是先调用getPages()来打开url,并将打开的网页存储到电脑缓存中,使用的的是urlretrieve()函数,接着使用savePages()将刚刚保存的网页保存到指定的硬盘位置。我们发现,利用urlretrieve()函数可以直接将下载的网页下载到给定的硬盘位置,所以可以使用download_pages()直接搞定了。

代码:

#-*- coding:utf-8 -*-
#注:这里,我把赶集网首页称为主界面,首页里的公司链接及其页面称为子界面
import os
import re
import sys
import xlwt
import xlrd
import threading
from bs4 import BeautifulSoup
from time import sleep, ctime
from urllib import urlopen, urlretrieve

reload(sys)
sys.setdefaultencoding('utf-8')

class GanjiwangCrawler(threading.Thread):
	#url表示下载的主界面,mark标识是哪个进程下载的
	#location表明下载文件存储的文件夹,exname表明最后保存的Excel名
	#wb是创建的Excel对象,ws是对应的sheet对象
	def __init__(self, url, mark, location, exname, ws, wb):
		threading.Thread.__init__(self)
		self.url = url
		self.mark = mark
		self.location = location
		self.suburls = []
		self.exname = exname
		self.wb = wb
		self.ws = ws

	def run(self):
		#先下载主界面
		self.download_pages(self.url, 'main%s.txt'%str(self.mark), self.location)
		#分析主界面并返回主界面中包含的公司url
		self.suburls = self.analysis_main_pages('main%s.txt'%str(self.mark), self.location)
		#第一行依据suburls下载子界面 #第二行分析子界面并写入Excel中
		for i,su in enumerate(self.suburls):
			self.download_pages(su,r'file%s%s.txt'%(str(self.mark),str(i)), self.location)
			self.analysis_sub_pages(r'file%s%s.txt'%(str(self.mark),str(i)), self.location)

	def analysis_main_pages(self, fname, location):
		suburls = []
		filepath = location + fname
		if os.path.exists(filepath):
			fobj = open(filepath, 'r')
			lines = fobj.readlines()
			fobj.close()

			soup = BeautifulSoup(''.join(lines))
			leftBox = soup.find(attrs={'class':'leftBox'})
			list_ = leftBox.find(attrs={'class':'list'})
			li = list_.find_all('li')
			href_regex = r'href="(.*?)"'
			for l in li:
				suburls.append('http://bj.ganji.com' + re.search(href_regex,str(l)).group(1))
		else:
			print('The file is missing')
		#由于抓取的界面太多,导致赶集网会拒绝掉页面请求,这里我们修改下要抓取的公司数目(取十个)
		return suburls if len(suburls) < 10 else suburls[0:10]

	def download_pages(self, url, fname, location):
		try:
			urlretrieve(url, location + fname)
		except Exception, e:
			print 'Download page error:', url

	def write_to_excel(self, record, row):
		'该函数将给定的record字典中所有值存储到Excel相应的row行中'
		#写入公司名称
		companyName = record['companyName']
		self.ws.write(row,0,companyName)
		#写入服务特色
		serviceFeature = record['serviceFeature']
		self.ws.write(row,1,serviceFeature)
		#写入服务范围
		serviceScope = ','.join(record['serviceScope'])
		self.ws.write(row,2,serviceScope)
		#写入联系人
		contacts = record['contacts']
		self.ws.write(row,3,contacts.decode("utf-8"))
		#写入商家地址
		address = record['address']
		self.ws.write(row,4,address.decode("utf-8"))
		#写入聊天QQ
		qqNum = record['qqNum']
		self.ws.write(row,5,qqNum)
		#写入联系电话
		phoneNum = record['phoneNum']
		phoneNum = str(phoneNum).encode("utf-8")
		self.ws.write(row,6,phoneNum.decode("utf-8"))
		#写入网址
		companySite = record['companySite']
		self.ws.write(row,7,companySite)
		self.wb.save(self.exname)

	def analysis_sub_pages(self, subfname, location):
		filepath = location + subfname
		f = open(filepath, 'r')
		lines = f.readlines()
		f.close()
		#建立一个BeautifulSoup解析树,并提取出联系店主模块的信息(li)
		try:
			soup = BeautifulSoup(''.join(lines))
			body = soup.body
			wrapper = soup.find(id="wrapper")
			clearfix = wrapper.find_all(attrs={'class':'d-left-box'})[0]
			dzcontactus = clearfix.find(id="dzcontactus")
			con = dzcontactus.find(attrs={'class':'con'})
			ul = con.find('ul')
			li = ul.find_all('li')
		except Exception, e:#如果出错,即该网页不符合我们的通用模式,就忽略掉
			return None
		#如果该网页不符合我们的通用模式,我们就取消掉这次的分析
		if len(li) != 10:
			return None
		#记录一家公司的所有信息,用字典存储,可以依靠键值对存取,也可以换成列表存储
		record = {}
		#公司名称
		companyName = li[1].find('h1').contents[0]
		record['companyName'] = companyName
		#服务特色
		serviceFeature = li[2].find('p').contents[0]
		record['serviceFeature'] = serviceFeature
		#服务提供
		serviceProvider = []
		serviceProviderResultSet = li[3].find_all('a')
		for service in serviceProviderResultSet:
			serviceProvider.append(service.contents[0])
		record['serviceProvider'] = serviceProvider
		#服务范围
		serviceScope = []
		serviceScopeResultSet = li[4].find_all('a')
		for scope in serviceScopeResultSet:
			serviceScope.append(scope.contents[0])
		record['serviceScope'] = serviceScope
		#联系人
		contacts = li[5].find('p').contents[0]
		contacts = str(contacts).strip().encode("utf-8")
		record['contacts'] = contacts
		#商家地址
		addressResultSet = li[6].find('p')
		re_h=re.compile('</?\w+[^>]*>')#HTML标签
		address = re_h.sub('', str(addressResultSet))
		record['address'] = address.encode("utf-8")
		restli = ''
		for l in range(8,len(li) - 1):
			restli += str(li[l])
		#商家QQ
		qqNumResultSet = restli
		qq_regex = '(\d{5,10})'
		qqNum = re.search(qq_regex,qqNumResultSet).group()
		record['qqNum'] = qqNum
		#联系电话
		phone_regex= '1[3|5|7|8|][0-9]{9}'
		phoneNum = re.search(phone_regex,restli).group()
		record['phoneNum'] = phoneNum
		#公司网址
		companySite = li[len(li) - 1].find('a').contents[0]
		record['companySite'] = companySite
		#将该公司记录存入Excel中
		openExcel = xlrd.open_workbook(self.exname)
		table = openExcel.sheet_by_name(r'CompanyInfoSheet')

		self.write_to_excel(record, table.nrows)

def init_excel(exname):
	'我们初试化一个表格,并给表格一个头部,所以我们给头部不一样的字体'
	wb = xlwt.Workbook()
	ws = wb.add_sheet(r'CompanyInfoSheet')
	#初始化样式
	style = xlwt.XFStyle()
	#为样式创建字体
	font = xlwt.Font()
	font.name = 'Times New Roman'
	font.bold = True
	#为样式设置字体
	style.font = font
	# 使用样式
	#写入公司名称
	ws.write(0,0,u'公司名称', style)
	#写入服务特色
	ws.write(0,1,u'服务特色', style)
	#写入服务范围
	ws.write(0,2,u'服务范围', style)
	#写入联系人
	ws.write(0,3,u'联系人', style)
	#写入商家地址
	ws.write(0,4,u'商家地址', style)
	#写入聊天QQ
	ws.write(0,5,u'QQ', style)
	#写入联系电话
	ws.write(0,6,u'联系电话', style)
	#写入网址
	ws.write(0,7,u'公司网址', style)
	wb.save(exname)
	return [ws, wb]

def main():
	'启动爬虫线程进行下载啦'
	exname = r'info.xls'
	print 'start crawler'
	excels = init_excel(exname)
	#初始化url
	urls = []
	#下载赶集网页面的个数,最多可以设为三百多,同时代表本次的线程数
	pages = 2
	nloops = xrange(pages)
	for i in nloops:
		url = 'http://bj.ganji.com/danbaobaoxian/o%s/' % str(i + 1)
		urls.append(url)

	threads = []
	for i in nloops:
		t = GanjiwangCrawler(urls[i], mark=i,location=r'pagestroage\\',exname=exname, ws=excels[0], wb=excels[1])
		threads.append(t)

	for i in nloops:
		threads[i].start()

	for i in nloops:
		threads[i].join()

	print 'OK, everything is done'
if __name__ == '__main__':
	main()

运行结果:

pagestroage文件夹下下载了两个main0.txt和main1.txt文件,对应两个线程。同时还下载了file0i.txt和file1j.txt文件,其中i从0到9,j也从0到9。也就是说两个线程最后从main文件中解析了url后各自下载了十个(我设定的)公司界面。info.xls中包含15条公司的记录。

我的文件目录:

插曲:

在自己开启多线程下载后发现,自己的程序经常一运行就直接退出,后来发现程序发起的url请求被赶集网给拒绝了,回复的都是机器人界面,如下图:

上图可见赶集网对抓取强度是有一定限制的,我们可以在程序中使用sleep语句来降低页面下载的速度。

后感:

考完研回校后做的第一个程序,终于认识到也有机会好好编程了。程序开发过程中总是遇到各种诡异的问题,什么编码问题,tab和空格混用问题。所幸后来都一一解决了。

未完待续。

时间: 2024-07-30 21:05:25

python爬虫Pragmatic系列IV的相关文章

python爬虫Pragmatic系列III

python爬虫Pragmatic系列III 说明: 在上一篇博客中,我们已经学会了从赶集网上的一家公司中提取出有关的信息,并存储到Excel中. 本次目标: 在本节中,我们将批量下载赶集首页上所有的公司界面(注意不是赶集网上所有的公司页面,我们可以把这个留给之后的任务),并批量的处理所有公司的有关信息,并保存到Excel中. 注意: 在上一篇博客中,我们使用的只是匹配赶集网上其中一家公司界面的中信息,而且不幸的是,很多的其他的公司的联系店主模块中的信息数量并不是固定的,即有的是10个li,而有

Python爬虫学习系列教程

Python爬虫学习系列教程 大家好哈,我呢最近在学习Python爬虫,感觉非常有意思,真的让生活可以方便很多.学习过程中我把一些学习的笔记总结下来,还记录了一些自己实际写的一些小爬虫,在这里跟大家一同分享,希望对Python爬虫感兴趣的童鞋有帮助,如果有机会期待与大家的交流. Python版本:2.7 一.爬虫入门 1. Python爬虫入门一之综述 2. Python爬虫入门二之爬虫基础了解 3. Python爬虫入门三之Urllib库的基本使用 4. Python爬虫入门四之Urllib库

Python爬虫Csdn系列I

Python爬虫Csdn系列I By 白熊花田(http://blog.csdn.net/whiterbear) 说明: 我会在这个系列介绍如何利用python写一个csdn爬虫,并将给定的Csdn用户的博客的所有文章保存起来.嗯,实用性貌似不是很大,写着玩,这个系列后,会有更好玩的更高级的爬虫出现. 原因: 本来想学cookie的,后来发现爬取csdn的文章伪装成浏览器去访问就行了. 本次目标: 爬取csdn某用户的文章列表.这里以我的blog为例,仅仅打开第一列文章列表,不做任何分析,只是验

Python爬虫Csdn系列III

Python爬虫Csdn系列III By 白熊花田(http://blog.csdn.net/whiterbear) 转载需注明出处,谢谢. 说明: 在上一篇博客中,我们已经能够获取一个用户所有文章的链接了,那么这一节自然就是要将这些博客下载下来咯. 分析: 有了链接下载文章自然是不难.但是,获取的数据该怎么处理?每一篇文章都带有格式换行这些信息,自然,我们存储它们也是要存储其对应的html格式的数据的(注意,我们编辑的带有格式的博客或者其他文本都是以html代码格式存储的).如何存?使用数据库

Python爬虫Csdn系列II

Python爬虫Csdn系列II By 白熊花田(http://blog.csdn.net/whiterbear) 转载需注明出处,谢谢. 说明: 在上一篇文章中,我们已经知道了只要将程序伪装成浏览器就能访问csdn网页.在这篇文章中,我们将设法获取某个csdn用户的所有文章的链接. 分析: 打开一个某一个的csdn用户的的专栏,可以选择目录视图(如:http://blog.csdn.net/whiterbear?viewmode=contents)和摘要视图(比如:http://blog.cs

《Python爬虫学习系列教程》学习笔记

转自:http://cuiqingcai.com/1052.html 大家好哈,我呢最近在学习Python爬虫,感觉非常有意思,真的让生活可以方便很多.学习过程中我把一些学习的笔记总结下来,还记录了一些自己实际写的一些小爬虫,在这里跟大家一同分享,希望对Python爬虫感兴趣的童鞋有帮助,如果有机会期待与大家的交流. 一.Python入门 1. Python爬虫入门一之综述 2. Python爬虫入门二之爬虫基础了解 3. Python爬虫入门三之Urllib库的基本使用 4. Python爬虫

Python爬虫开发系列之一》开发IDE安装

中国有句古话说:工欲善其事,必先利其器! 在我最开始学 Python 的时候,因为没有去探索好用的工具,吃了很多苦头.磕磕绊绊走过来之后才知道,好的工具给效率带来的提升不是从 1 到 1.1 倍速,而是从 1 到 10 倍速. 所以说编写和运行程序之前我们必须要先把开发环境配置好,只有配置好了环境并且有了更方便的开发工具我们才能更加高效地用程序实现相应的功能达到事半工倍的效果,然而很多情况下我们可能在最开始就卡在环境配置上,如果这个过程花费了太多时间,想必学习的兴趣就下降了大半,所以本章专门开发

Python爬虫开发系列之三》Requests请求库的使用

Requests是一个实用.简单.强大的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到.Requests 能够完全满足如今网络的需求.接下来我们从最基本的get post 请求开始 到高级特性一步一个脚印去学习.学习是个渐进的过程,只有脚踏实地不断的去练习才能掌握这些重要的知识点. 一.发送请求 get /post 按照规例首先要导入Requests模块>>> import requests r=requests.get('https://www.baidu.c

python爬虫beautifulsoup4系列1

前言 以博客园为例,爬取我的博客上首页的发布时间.标题.摘要,本篇先小试牛刀,先了解下它的强大之处,后面讲beautifulsoup4的详细功能. 一.安装 1.打开cmd用pip在线安装beautifulsoup4 >pip install beautifulsoup4 二.解析器 1.我们主要用第一个html.parser,这个是python的标准库,可以直接用.其它几个需要安装对应解析器, 下表列出了主要的解析器,以及它们的优缺点: 三.打印首页博客的时间 1.这里直接定位不好定位到,可以