python爬虫-->爬虫基础

时间:2021-07-20 06:33:52

在做机器学习,数据挖掘时,常常需要训练针对某个特定应用场景的模型,但是有时候又缺少我们想要的数据集,这个时候我们就要自己上某些特定的网站进行网络爬虫来获取大量数据。这也是我们学习python爬虫的一个动机和目标。
难点一:urllib2.urlopen与urllib2.request区别

import urllib2
response=urllib2.urlopen('http://www.example.com')
html=response.read()

这个过程就是我们平时刷网页的代码表现形式,它基于请求-响应模型。

response=urllib2.urlopen(‘http://www.example.com‘)
实际上可以看作两个步骤:
1. 我们指定一个域名并发送请求
request=urllib2.request(‘http://www.example.com‘)
2. 接着服务端响应来自客户端的请求
response=urllib2.urlopen(request)

也许你会注意到,我们平时除了刷网页的操作,还有向网页提交数据。这种提交数据的行为,urllib2会把它翻译为:

import urllib
import urllib2
url = 'http://www.example.com'
info = {'name' : 'Michael Foord',
'location' : 'Northampton'}
data = urllib.urlencode(info) #info 需要被编码为urllib2能理解的格式,这里用到的是urllib
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()

有时你会碰到,程序也对,但是服务器拒绝你的访问。这是为什么呢?问题出在请求中的头信息(header)。
有的服务端有洁癖,不喜欢程序来触摸它。这个时候你需要将你的程序伪装成浏览器来发出请求。请求的方式就包含在header中。
常见的情形:

import urllib
import urllib2
url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'# 将user_agent写入头信息
values = {'name' : 'Michael Foord',
'location' : 'Northampton',
'language' : 'Python' }
headers = { 'User-Agent' : user_agent }
data = urllib.urlencode(values)
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
the_page = response.read()

难点二:为什么要自定义opener对象?

    request=urllib2.Request(url,data,header)
opener=urllib2.build_opener()
if proxy:
proxy_name={urlparse.urlparse(url).scheme:proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_name))
try:
response=opener.open(request)
html=response.read()

当你获取一个URL你使用一个opener(一个urllib2.OpenerDirector的实例,urllib2.OpenerDirector可能名字可能有点让人混淆。)正常情况下,我们使用默认opener – 通过urlopen,但你能够创建个性的openers,Openers使用处理器handlers,所有的“繁重”工作由handlers处理。每个handlers知道如何通过特定协议打开URLs,或者如何处理URL打开时的各个方面,例如HTTP重定向或者HTTP cookies。

如果你希望用特定处理器获取URLs你会想创建一个openers,例如获取一个能处理cookie的opener,或者获取一个不重定向的opener。

要创建一个 opener,实例化一个OpenerDirector,然后调用不断调用.add_handler(some_handler_instance).

同样,可以使用build_opener,这是一个更加方便的函数,用来创建opener对象,他只需要一次函数调用。

build_opener默认添加几个处理器,但提供快捷的方法来添加或更新默认处理器。

其他的处理器handlers你或许会希望处理代理,验证,和其他常用但有点特殊的情况。

install_opener 用来创建(全局)默认opener。这个表示调用urlopen将使用你安装的opener。

Opener对象有一个open方法,该方法可以像urlopen函数那样直接用来获取urls:通常不必调用install_opener,除了为了方便。

下面完整代码解析:
下面代码就是用来存储以http://example.webscraping.com为首个网址,然后存储其网页html里面的子网址,依次存储其子网页html里面的网址链接。直到其深度达到maxdepth。用各种数据结果来存储中间变量和所有的链接网址

python爬虫-->爬虫基础

#coding:utf-8
import re
import urlparse
import urllib2
import time
from datetime import datetime
import robotparser
import Queue

def link_crawler(seed_url,link_regex=None,delay=5,max_depth=-1,max_url=-1,header=None,user_agent='wswp',proxy=None,num_retries=2):
crawl_queue=Queue.deque([seed_url]) ## 建立队列,并将seed_url插入队列
seen={seed_url:0} ## 初始化seed_url 深度为0
num_url=0 ##抓取的url数量
rp=get_robots(seed_url) ##获取这个网页的robots.txt文件
throttle=Throttle(delay)
headers=header or {}
if user_agent:
headers['User-agent']=user_agent ##用户代理
while crawl_queue:
url=crawl_queue.pop() ##出队列,获取队列内的url
if rp.can_fetch(user_agent,url): ##获取这个url的robots.txt文件
throttle.wait(url) ##爬虫延迟一会 ,防止被封
html=download(url,headers,proxy=proxy,num_retries=num_retries) ##下载该url的网页
links=[] ##用来保存该html内的所有网页链接

depth=seen[url] ##获取该url对应的深度
if depth !=max_depth:
if link_regex:
## 首先使用get_link方法获取该html内的所有的网页链接,然后检查该链接是否匹配link_regex。
#注意这里是用re.match,是从字符串首位就开始匹配(index|view),故是相对路径网址。
#所以后面需要normalize(seed_url,link)
## 如果符合则进links列表。这里需要注意list append和extend方法区别
links.extend(link for link in get_link(html) if re.match(link_regex,link))
for link in links:
link=normalize(seed_url,link) ##去除网页链接#后的fragment部分,获取url和seed_url连接获取绝对路径
if link not in seen: ##如果link不在seen中,即没有对他进行过爬取
seen[link]=depth+1 ##深度加1
if same_domain(seed_url,link): ##检查是否是同一个域名
crawl_queue.append(link) ##将符合条件的url加入队列中
num_url+=1
if num_url==max_url:
break
else:
print 'Blocked by robots.txt',url

class Throttle:
##休眠类,因为我们在之前设置了,爬取的网址的域名是一致的,每次记录这个域名被爬取的时间点
def __init__(self,delay):
self.delay=delay
self.domains={}

def wait(self,url):
##ParseResult(scheme='http', netloc='www.baidu.com', path='/', params='', query='ref=toolbar', fragment='')
##scheme是协议,netloc是服务器地址,path是相对路径,params是参数,query是查询的条件。
domain=urlparse.urlparse(url).netloc ##获取url的域名
last_accessed=self.domains.get(domain) ##获取该域名上一次爬取的时间点

if self.delay>0 and last_accessed is not None:
sleep_secs=self.delay-(datetime.now-last_accessed).seconds
if sleep_secs>0:
time.sleep(sleep_secs)
self.domains[domain]=datetime.now()

def download(url,header,proxy,num_retries,data=None):
##使用代理,模仿浏览器行为下载网页
print "Downloading:",url
request=urllib2.Request(url,data,header)
opener=urllib2.build_opener()
if proxy:
proxy_name={urlparse.urlparse(url).scheme:proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_name))
try:
response=opener.open(request)
html=response.read()
code=response.code
except urllib2.URLError as e:
print 'Download error:',e.reason
html=''
if hasattr(e,'code'):
code=e.code
if num_retries>0 and 500<= code <600:
return download(url,header,proxy,num_retries-1,data)
else:
code=None
return html

def normalize(seed_url,link):
## http://www.example.com/index.html#print,以#为分隔符,返回元组(url,fragment)
## http://www.example.com/index.html#print就代表网页index.html的print位置。
# 浏览器读取这个URL后,会自动将print位置滚动至可视区域。
link,_ =urlparse.urldefrag(link)
return urlparse.urljoin(seed_url,link) #相对网址连接成绝对网址

def same_domain(url1,url2):##查看两个url的域名是否一样
return urlparse.urlparse(url1).netloc==urlparse.urlparse(url2).netloc

def get_robots(url):
rp=robotparser.RobotFileParser()
rp.set_url(urlparse.urljoin(url,'/robots.txt'))
rp.read()
return rp

def get_link(html):
##返回这个html内的所有网页链接
## 使用正则表达式
webpage_regx=re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
return webpage_regx.findall(html)

if __name__=='__main__':
link_crawler2('http://example.webscraping.com','/(index|view)',delay=0,num_retries=1,user_agent='GoodCrawler')
link_crawler2('http://www.baidu.com','/(index|view)',delay=0,num_retries=1)