1、访问链接,获得链接网页源码
难点:反爬虫
可以用headers和proxy解决
2、识别网页中的链接
链接形式不统一,有绝对链接和相对链接,urljoin()
3、链接储存
分为两部分,第一部分是进行识别链接是否已经被访问,第二部分进行储存所有链接
(1): 运用crawl_queue堆栈,将未被访问的链接进栈,在crawl_queue非空时,出栈一个url,访问此url并获取新的url,以此进行循环
(2):将所有url储存到link_save字典中
测试CPU:i5-6300hq;RAM: 8GB
测试链接:https://zhuanlan.zhihu.com/p/35324806
测试结果:爬取183个url
测试时间:386.641009527s
代码:
from urllib.request import urlopen, URLError, HTTPError, Request, build_opener, ProxyHandler
from urllib.parse import urlparse,quote,urljoin
import re
from datetime import datetime
import time
#第一个参数是要爬取的url,第二个参数是代理,第三个参数是重试次数
def download(url, proxy=None, num_retries=2):
print('Downloading', url)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'}
request = Request(url, headers=headers) #加入headers进行浏览器模拟
opener = build_opener()
if proxy:
opener.add_handler(ProxyHandler({urlparse(url).scheme : proxy})) #urlparse(url).scheme是url的scheme,即协议,如http;proxy是代理服务器IP+Port
#ProxyHandler({urlparse(url).scheme : proxy}) 是建一个Handler处理器对象,参数是一个字典类型,包括代理类型和代理服务器IP+Port
#add_handler(ProxyHandler({urlparse(url).scheme : proxy}))是使用代理
try:
html = opener.open(request).read().decode() #访问、读取、解码url
except (URLError, HTTPError, UnicodeDecodeError, UnicodeEncodeError) as e: #出现访问url错误时,捕获括号里的错误储存到e里
print('Download error:', e.reason)
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600: #hasattr() 函数用于判断对象是否包含对应的属性。
#HTTPError实例产生后会有一个code属性,这就是是服务器发送的相关错误号。
#x and y 的值只可能是x或y. x为真就是y, x为假就是x; x and y 的值只可能是x或y. x为真就是y, x为假就是x
return download(url, num_retries - 1) #递归
return html
def get_link(html):
webpage_regex = re.compile('<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE) #'<a[^>]+href=["\'](.*?)["\']'表示匹配模式
try:
return webpage_regex.findall(html)
except TypeError:
return []
# 限速
class Throttle:
def __init__(self, delay):
self.delay = delay
self.domains = {}
def wait(self, url):
domain = urlparse(url).netloc #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) # time sleep() 函数推迟调用线程的运行,可通过参数secs指秒数,表示进程挂起的时间。
self.domains[domain] = datetime.now()
# 爬取链接,保存到link_save
#第一个参数是要爬取的url,第二个参数是匹配链接的正则表达式,第三个参数是爬取的深度
def link_crawler(seed_url, link_regex, max_depth=2):
crawl_queue = [seed_url]
link_save = {seed_url:0}
throttle = Throttle(2)
while crawl_queue:
url = crawl_queue.pop() #弹出没有访问过的网站,进行访问
depth = link_save[url]
throttle.wait(url)
html = download(url)
if depth <= max_depth:
for link in get_link(html):
# 相对链接使用这行代码合并绝对链接
#link = urljoin(seed_url, link)
if re.match(link_regex, link) and link not in link_save.keys(): #判断爬取的link的开头是否为link_regex,并且判断爬取的link是否已经保存
crawl_queue.append(link)
link_save[link] = depth + 1
return link_save
if __name__ == "__main__":
result = link_crawler("http://www.xxxx.com/", "http://.*?")
with open('xxxx_com.txt', 'w') as f:
for i in result.keys():
f.writelines(f'{i}\n')
正则表达式模式:
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符
[...] 用来匹配一组字符,如[amk]将匹配对象中存在的‘a’ 'm' 'k'都匹配出来
[^...] 匹配不在[]中的字符
a|b 匹配a或b
[a-z] 匹配任何小写字母
[A-Z] 匹配任何大写字母
\d 匹配一个数字字符
["\'] 匹配单双引号