1.使用id遍历
(1)原理
使用id遍历网页是常见的做法,由于大多数网站存储的数据太多,不可能为每一个网页都起名字,便用id做标记使得数据库方便识别,这也使得按id遍历网页成为可能。
在示例网站:http://example.python-scraping.com
我们可以用两种方式访问同一网页:http://example.python-scraping.com/view/Afghanistan-1
http://example.python-scraping.com/view/1
所以我们只要每次改变一下尾部的id就可以方便的遍历该网站的所有国家页面
(2)问题
避免因为某个页面被删除、隐藏或者id的缺号而意外终止,设置最大出错数,不至于一遇到id缺号就停止,如此一来只有连续出错多次程序才会结束
由于不知道id到哪里结束,我们还需要一个无线迭代器,产生从1开始的整数逐步增大直到程序停止。使用itertools模块。该模块是一个无穷迭代器,其中itertools.count(start [,step])函数创建一个从start开始,步长为step的迭代器,默认为1
(3)实现
import itertools
import urllib.request
from urllib.error import URLError, HTTPError, ContentTooShortError
import re
#以Chrom为默认代理,默认重试两次的爬取网页封装函数;获取网页指定的解码格式,否则使用默认的'utf-8'格式
def GetData(url, proxy='', retry=2, charset = 'utf-8'):
print('download : ' + url)
if proxy == '':
proxy = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11'
request = urllib.request.Request(url)
request.add_header('User-Agent', proxy)
try:
response = urllib.request.urlopen(request)
cs = response.headers.get_content_charset()
if not cs:
cs = charset
html = response.read().decode(cs)
# print(html) 当前只是打印页面,之后可以修改为保存爬取的页面
except (URLError, HTTPError, ContentTooShortError) as e:
print('download error :', e.reason)
if retry > 0: # 递归重试下载
if hasattr(e, 'code') and 500 < e.code < 600:
GetData(url, proxy, retry - 1)
return html
#利用字符串的format函数拼接url和id,避免因为某个页面被删除、隐藏或者id的缺号而意外终止
# 设置最大出错数,即max_error默认为5,不至于一遇到id缺号就停止
def scrap_byID(url, max_error=5):
for page in itertools.count(1):
pg_url = '{}{}'.format(url,page)
html = GetData(pg_url)
if html is None:
max_error -= 1
if max_error == 0:
break
url = 'http://example.python-scraping.com/view/'
scrap_byID(url)
2.追踪链接
跟踪网页中的链接,利用正则表达式只访问感兴趣的内容。其实就是获取起始页面中的所有链接,通过正则表达式过滤出我们感兴趣的链接访问,再新打开的页面中再做同样的事情,从而追踪下去。
(1)用到的库及遇到的问题
一、正则表达式的设定和匹配
当我们在Python中使用正则表达式时,re模块内部会干两件事情:
编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
用编译后的正则表达式去匹配字符串。
那么如果一个正则表达式要重复使用几千次,出于效率的考虑,我们是不是应该先把这个正则先预编译好, 接下来重复使用时就不再需要编译这个步骤了,直接匹配,提高我们的效率。
1.compile(pattern, flags=0)函数,预编译十分的简单,re.compile()即可,编译后生成Regular Expression对象。 pattern 指定编译时的表达式字符串 flags 编译标志位,用来修改正则表达式的匹配方式。支持 re.L|re.M 同时匹配 :
- re.I(re.IGNORECASE) :使匹配对大小写不敏感
- re.L(re.LOCAL) :做本地化识别(locale-aware)匹配
- re.M(re.MULTILINE) :多行匹配,影响 ^ 和 $
- re.S(re.DOTALL) :使 . 匹配包括换行在内的所有字符
- re.U(re.UNICODE):根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
- re.X(re.VERBOSE):该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。
2.re.match(正则表达式,要匹配的字符串)函数进行匹配
3.re库的findall(pattern, string, flags=0)函数,返回string中所有与pattern匹配的全部字符串,返回形式为列表。
二、正则匹配的链接标签
1.^在正则表达式中的应用
表示起始定界符
如:'abc'表示字符串中有'abc'就匹配成功
'[abc]'表示字符串中有'a'或'b'或'c'就匹配成功
'^abc'表示字符串由'abc'开头就匹配成功
'^[abc]'表示字符串由'a'或'b'或'c'开头的,
表示取反
当^表示取反的时候,只有一种情况,就是在中括号里面,而且是每一个字符之外的。 所以千万不要把'[^abc]'看成是对'abc'字符串的取反。如:
'[^abc]'表示匹配'a','b','c'之外的字符。如果一个字符串是由'a','b','c'组合起来的,那就是假。
2.a标签的格式
这是抓取的html页面中a标签的形式<a href="/places/default/view/Afghanistan-1">Afghanistan</a>
href指向的就是链接,可以很明显看到这里给出的是相对链接,即相对主页面的链接
所以我们要对链接做一些拼接操作,将诸如'http://www.xxx.com/'拼接在前面,
拼接用到的模块为urllib.parse的urljoin函数 #urljoin('index1', 'index2')连接两个参数的url, 将第二个参数中缺的部分用第一个参数的补齐,如果第二个有完整的路径,则以第二个为主。
三、定义多行字符串
使用三引号
如:str1 = """Le vent se lève, il faut tenter de vivre.
起风了,唯有努力生存。
(纵有疾风起,人生不言弃。)""" 三对 双引号单引号都可以
使用小括号
如: str3 = ('Le vent se lève, il faut tenter de vivre.'
'起风了,唯有努力生存。'
'(纵有疾风起,人生不言弃。)')
这样做的好处是避免了对特殊字符的转义
四、重复下载问题、
由于这些链接之间有可能重复,比如a页面有链接指向b页面,b页面也有链接指向a页面,有可能会重复下载甚至死循环,我们要避免这种情况 。设置一个seen队列储存已经存储过的页面,这样新链接到达时只要不在seen队列中,就意味着没有下载过。
(2)代码实现
import re
from urllib.parse import urljoin
import urllib.request
from urllib.error import URLError, HTTPError, ContentTooShortError
#以Chrom为默认代理,默认重试两次的爬取网页封装函数;获取网页指定的解码格式,否则使用默认的'utf-8'格式
def GetData(url, proxy='', retry=2, charset = 'utf-8'):
print('download : ' + url)
if proxy == '':
proxy = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11'
request = urllib.request.Request(url)
request.add_header('User-Agent', proxy)
try:
response = urllib.request.urlopen(request)
cs = response.headers.get_content_charset()
if not cs:
cs = charset
html = response.read().decode(cs)
# print(html) 当前只是打印页面,之后可以修改为保存爬取的页面
except (URLError, HTTPError, ContentTooShortError) as e:
print('download error :', e.reason)
if retry > 0: # 递归重试下载
if hasattr(e, 'code') and 500 < e.code < 600:
GetData(url, proxy, retry - 1)
return html
#获取一个页面跟踪到的符合正则表达式的链接,此正则式仅针对链接后的目录
def scrap_link(start_url, link_regex):
crawl_queue = [start_url]
seen = [start_url] # seen = set(crawl_queue) set函数返回‘set’对象,可以用seen.add()添加元素
while crawl_queue :
url = crawl_queue.pop()
html = GetData(url)
if html is None:
continue
for link in getlinks(html):
print(link)
print(re.match(link_regex, link))
if re.match(link_regex,link):
abs_link = urljoin(start_url, link)
if abs_link not in seen:
crawl_queue.append(abs_link)
seen.append(abs_link)
#返回一个页面的所有a标签的链接
def getlinks(html):
url_regex = re.compile("""<a[^>]+href=["'](.*?)["']""", re.IGNORECASE)
return url_regex.findall(html) #或者re.findall(url_regex, html, re.IGNORECASE)
#调用函数
scrap_link('http://example.python-scraping.com', '/places/default/(index|view)/')