最近在学习python实现爬虫,以此文章记录下学习历程
爬虫功能
简而言之就是对网上数据爬取并分析,因此实现爬虫需要对网页下载,再提取所需数据、分析。
网页下载
这里书上利用urllib库实现,首先我们定义一个下载函数:
def download(url, user_agent='gdcrawler', proxy=None, num_retries=2): # 网页下载函数
print('download:', url)
headers = {'User_agent': user_agent}
request = urllib.request.Request(url, headers=headers) # 创建Request对象,设置请求头
opener = urllib.request.build_opener()
if proxy:
proxy_params = {urllib.parse.urlparse(url).scheme: proxy} # 创建一个代理字典
proxy1 = urllib.request.ProxyHandler(proxy_params) # 创建一个代理请求
opener = urllib.request.build_opener(proxy1) # 用新代理覆盖原来opener
print(proxy_params)
try:
html = opener.open(request).read() # 利用urllib对网站下载
except urllib.error as e: # 处理异常
print("download error:", e)
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600: # hasattr 用于检测对象中是否有相应属性,5xx时为服务端问题,需重新下载
return download(url, user_agent, num_retries-1) # 再次执行函数
return html
函数首先设置请求头相应信息,支持代理,再对服务器请求下载网页,如果出现异常,再判断是否为服务端问题,若是则重新下载。
对于书上举例网址http://example.webscraping.com,
download('http://example.webscraping.com')
输出
下载限速
如果对网站爬取速度过快,就会面临被封禁或造成服务器过载的风险,因此应该限制爬虫爬取速度。
class Throttle(object): # 用于下载限速
def __init__(self, delay):
self.delay = delay
# 保留上一次完成的时间的时间戳
self.domains = {}
def wait(self, url):
domain = urllib.parse.urlparse(url).netloc # 获得域名信息,对于同一网站来说是一样的
last_successed = self.domains.get(domain) # 因此可根据此获得上次的时间,因为每次都会覆盖
if self.delay > 0 and last_successed is not None: # 这里保证了访问新网址时便直接跳过循环
sleep_secs = self.delay-(time.time()-last_successed)
# 判断时间是否符合延迟设定
if sleep_secs > 0:
time.sleep(sleep_secs)
self.domains[domain] = time.time() # 对于同一网址这将使得它被覆盖
记录下每次下载的时间,将其与下一次下载时间比较,判断是否满足下载的限制时间,使用时将其放于下载前即可。
链接爬虫
书上举了三种方法下载网站的所有网页
- 网站地图爬虫
- ID遍历爬虫
- 链接爬虫
自我感觉链接爬虫更加有用且全面,代码如下
def link_crawler(seed_url, link_regex, max_depth): # 前一个参数即根链接,后一个参数为用于跟踪的正则表达式,添加深度
# 此操作可以将以此正则表达式开头的的网址都下载一遍
craw_queue = [seed_url]
# seen = set(craw_queue) # 创建一个集合,用于判断是否循环操作,对于字典的key也不允许有相同值,所以也可用字典
seen = {seed_url: 0} # 将根链接深度设为0,存在字典中
rp = urllib.robotparser.RobotFileParser() # 这里往往会出现urllib不包含这些包,需要手动在开头导入,用于查看robots协议,是否能爬
seed_urltxt = seed_url+'/robots.txt'
rp.set_url(seed_urltxt)
rp.read() # 这里为什么是None
while craw_queue:
url = craw_queue.pop() # 移除列表中最后一项
depth = seen[url]
if rp.can_fetch(user_agent, seed_url):
# t.wait(url) # 放于下载前,达到延迟效果
# print(t.domains)
html = download(url)
if depth != max_depth:
for link in get_links(html):
if re.match(link_regex, link):
link = urllib.parse.urljoin(seed_url, link)
if link not in seen: # 排除掉之前所下载过的链接
# seen.add(link) # 每出现一次新连接都加到集合中,用于下一次判断
seen[link] = depth + 1
craw_queue.append(link) # 如果网址很多,这将会是一个很大的值
else:
print('Blocked by robots.txt:', seed_url) # 不能爬则输出此行
# link1 = seed_url
# link1 = link1+link # 目的是变成绝对路径,课本用的另一种方法
# craw_queue.append(link1)
def get_links(html): # 获得网站信息中的网址
webpage_regex = re.compile('<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE) # 其中[^>]代表除>以外的所有字符,["\']代表其中任一字符,
# re.IGNORECASE为忽略大小写,上式即是a标签正则表达式
try:
return webpage_regex.findall(html.decode())
except Exception as e:
print(e)
上面是提取出网页内a标签里所有网址,再对满足要求的网址下载,并循环下去。为了避免爬虫陷阱,(即网站动态生成网页内容,出现无限多的网页,使得链接爬虫无止境的下载网页)。上面定义了下载深度,达到深度后便会停止。
link_crawler('http://example.webscraping.com', '/places/default/(view|index)', 1)
输出
数据抓取
- 正则表达式
- Beautiful Soup
- Lxml
正则:
# 利用正则表达式和链接爬虫将国家和面积输出
for ht in link_crawler('http://example.webscraping.com', '/places/default/(view|index)', 1):
x = re.findall('<td class="w2p_fw">(.*?)</td>', str(ht))
# print(str(x))
area = re.findall(', \'(.*? square kilometres)', str(x))
# print(area)
country = re.findall('<tr id="places_country__row">.*?<td class="w2p_fw">(.*?)</td>', str(ht))
# print(country)
down[str(country)] = str(area)
上面为了使link_crawler可迭代,在link_crawler后加了yield html
大致思想就是对下载的网页匹配所需内容,只是这三种匹配方式不同,正则和lmxl效率更高。
数据存储
可将获得的数据保存起来,此例中,将国家等相应信息保存为csv文件
import numpy as np
import pandas as pd
class Store(object): # 创建了一个类,用于将所爬取的数据保存到csv中,缺点是必须知道有哪些属性,且每一个网页都是差不多的
def __init__(self):
lt = ['area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours']
self.lt = lt
self.s = pd.DataFrame(columns=self.lt) # 创建了一个dataframe
def write(self, html):
ht = lxml.html.fromstring(html)
t = [] #创建一个列表用来储存爬出的数据
for item in self.lt:
# print(self.lt)
# print(item)
try:
txt = str(ht.cssselect('#places_{}__row > td.w2p_fw'.format(item))[0].text_content()) # 若没有匹配到则跳过
# print(txt)
t.append(txt)
except Exception as e:
print(e)
# print(t)
try:
ser = pd.Series(index=self.lt, data=t)
self.s = self.s.append(ser, ignore_index=True) # 必须要有ignore_index
# print(self.s)
except Exception as e:
print(e)
self.s.to_csv('data.csv', index=False)
st = Store()
for html in link_crawler('http://example.webscraping.com', '/places/default/view', 1):
st.write(html)
书中后续还有许多内容,比如下载缓存,并发下载,动态内容等等,有时间看