五年Python爬虫程序员整理的全栈爬虫知识点

1 最简单的单页面抓取

思路:

  • 获取页面所有url
  • 对获取的所有url进行分类
  • A 获取属于本域名下的url
  • B 获取属于其他url

2 用到的模块

  • urllib
  • bs4
  • re正则表达式
五年Python爬虫程序员整理的全栈爬虫知识点

学习Python中的小伙伴,需要学习资料的话,可以到我的微信公众号:Python学习知识圈,后台回复:“01”,即可拿Python学习资料


3 代码说明:

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import urllib
from bs4 import BeautifulSoup
import re
def get_all_url(url):
urls = []
web = urllib.urlopen(url)#使用urllib模块的urlopen函数打开url,复制给web
soup =BeautifulSoup(web.read())#将web内容转化为beautigulsoup格式的数据。

通过正则过滤合理的url(针对与freebuf.com来讲)

tags_a =soup.findAll(name='a',attrs={'href':re.compile("^https?://")})

soup.findall函数的运用,配合正则表达式来过滤url

try :
for tag_a in tags_a:

re:^ 表示匹配字符串开头例如,^ abc 匹配 abc

? 表示匹配前一个字符串0次或1次,例如 abc? 匹配 ab 和 abc

return urls

except:
pass
return urls

得到所有freebuf.com下的url

def get_local_urls(url):
local_urls = []
urls = get_all_url(url)
for _url in urls:
ret = _url
if 'freebuf.com' in ret.replace('//','').split('/')[0]:
local_urls.append(_url)
return local_urls

if 'freebuf.com' in ret.replace('//','').split('/')[0]:这个if语句不是很明白,通过split()函数,把域名分割,获取分割后组成的类表的第一个字符串。但是在分割前为什么要把//替换成空格???

得到所有的不是freebuf.com域名的url

def get_remote_urls(url):
remote_urls = []
urls = get_all_url(url)
for _url in urls:
ret = _url
if "freebuf.com" not in ret.replace('//','').split('/')[0]:
remote_urls.append(_url)
return remote_urls

主函数

def main():
url = 'http://freebuf.com/'
rurls = get_remote_urls(url)
print "--------------------remote urls-----------------------"
for ret in rurls:
print ret
print "---------------------localurls-----------------------"
lurls = get_local_urls(url)
for ret in lurls:
print ret
if name == 'main':
main()
</pre>

上面是单独对一个页面抓取。如果对整个站点抓取的话,还设计到url的处理,用作者的原话:

我们可以把整站当成一个错综复杂的图结构,有一些算法基础的读者都会知道图的简单遍历方法:dfs和bfs(深度优先和广度优先)。如果这里读者有问题的话建议先去学习一下这两种算法。大体的算法结构我们清楚了,但是在实现中我们显然需要特殊处理url,需要可以区分当前目标站点域名下的网站和其他域名的网站,除此之外,在href的值中经常会出现相对url,这里也要特别处理。

下面是代码:

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import urllib
from bs4 import BeautifulSoup
import urlparse
import time
import urllib2

urllib 和 urllib2的区别http://blog.csdn.net/dolphin_h/article/details/45296353

url = "http://xxxx.xx/"
domain = "xxxx.xx"
deep = 0
tmp = ""
sites = set()
visited = set()

local = set()

集合:

python的set和其他语言类似, 是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素. 集合对象还支持union(联合), intersection(交), difference(差)和sysmmetric difference(对称差集)等数学运算.
def get_local_pages(url,domain):
global deep
global sites
global tmp
repeat_time = 0
pages = set()

防止url读取卡住

while True:
try:
print "Ready to Open the web!"
time.sleep(1)

time.sleep()函数,

Python time sleep() 函数推迟调用线程的运行,可通过参数secs指秒数,表示进程挂起的时间
print "Opening the web", url
web = urllib2.urlopen(url=url,timeout=3)
print "Success to Open the web"
break
except:
print "Open Url Failed !!! Repeat"
time.sleep(1)
repeat_time = repeat_time+1
if repeat_time == 5:
return

上面整段判断url能不能打开

print "Readint the web ..."
soup = BeautifulSoup(web.read())
print "..."

提取标签 a

for tag in tags:

避免参数传递异常

try:
ret = tag['href']
except:
print "Maybe not the attr : href"
continue
o = urlparse.urlparse(ret)

urlparse.urlparse函数的使用

urlparse模块主要是把url拆分为6部分,并返回元组。并且可以把拆分后的部分再组成一个url。主要有函数有urljoin、urlsplit、urlunsplit、urlparse等。
urlparse.urlparse(urlstring[, scheme[, allow_fragments]])
将urlstring解析成6个部分,它从urlstring中取得URL,并返回元组 (scheme, netloc, path, parameters, query, fragment),但是实际上是基于namedtuple,是tuple的子类。它支持通过名字属性或者索引访问的部分URL,每个组件是一串字符,也有可能是空的。组件不能被解析为更小的部分,%后面的也不会被解析,分割符号并不是解析结果的一部分,除非用斜线转义,注意,返回的这个元组非常有用,例如可以用来确定网络协议(HTTP、FTP等等 )、服务器地址、文件路径,等等。
"""

Debug I/O

for _ret in o:
if _ret == "":
pass
else:
print _ret
"""

处理相对路径url

if o[0] is "" and o[1] is "":
print "Fix Page: " +ret
url_obj = urlparse.urlparse(web.geturl())

获取web页面的url,用urlparse函数抽离

ret = url_obj[0] + "://" + url_obj[1] + url_obj[2] + ret

组成一个绝对url

保持url的干净

ret = ret[:8] + ret[8:].replace('//','/')
o = urlparse.urlparse(ret)

这里不是太完善,但是可以应付一般情况

if '../' in o[2]:
paths = o[2].split('/')
for i inrange(len(paths)):
if paths[i] == '..':
paths[i] = ''
if paths[i-1]:
paths[i-1] = ''
tmp_path = ''
for path in paths:
if path == '':
continue
tmp_path = tmp_path + '/' +path
ret =ret.replace(o[2],ret_path)
print "FixedPage: " + ret
上面整段都是判断获到的url是绝对url还是相对url。如果是相对url,则还需进行重组成完善的绝对url。包括url中含../的情况进行处理。但是处理../的情况不理解!!???

协议处理

if 'http' not in o[0]:
print "Bad Page:" + ret.encode('ascii')
continue

url合理性检验

if o[0] is "" and o[1] is not "":
print "Bad Page: " +ret
continue

域名检验

if domain not in o[1]:

变量domain用来检验获取的所有url是否是改域名下的

print "Bad Page: " +ret
continue

整理,输出

newpage = ret
if newpage not in sites:
print "Add New Page: " + newpage
pages.add(newpage)
return pages

dfs算法遍历全站

def dfs(pages):

无法获取新的url说明便利完成,即可结束dfs

if pages is set():
return
global url
global domain
global sites
global visited
sites = set.union(sites,pages)
for page in pages:
if page not in visited:
print "Visiting",page
visited.add(page)
url = page
pages = get_local_pages(url, domain)
dfs(pages)
print "sucess"

整段程序下来,一直不知道 变量domain用来检验获取的所有url是否是改域名下的

pages = get_local_pages(url, domain)
dfs(pages)
for i in sites:
print i
</pre>

整个的大概思路是:

  • 传入url
  • 判断能否打开
  • 打开url,获取整个改url的web信息
  • 提取属性‘href’
  • 遍历提取到的链接,这里叫做url2
  • 判断url是否为相对url
  • 是的话
  • 对其进行抽离[urlparse.urlparse()]
  • 重组
  • (处理相对url)
  • 最后检验协议、url域名。
  • 带入遍历算法。
  • 最后是web元素的处理

两个模块

bs4模块和docx模块的使用

**bs4前面有写

我们重点要讲findAll方法的两个参数:name和attr**

Name: 指的是标签名,传入一个标签名的名称就可以返回所有固定名称的标签名

Attr: 是一个字典存储需要查找的标签参数,返回对应的标签

  • Tag.children 表示获取tag标签的所有子标签
  • Tag.string 表示获取tag标签内的所有字符串,不用一层一层索引下去寻找字符串
  • Tag.attrs[key] 表示获取tag标签内参数的键值对键为key的值
  • Tag.img 表示获取tag标签的标签名为img的自标签(一个)

docx模块:

在使用这个模块的时候,要记清楚如果:

pip install python-docx

easy_install python-docx

两种方式安装都不能正常使用的话,就需要下载tar包自己手动安装

Docx模块是一个可以直接操作生成docx文档的python模块,使用方法极尽简单:

  • Demo = Document() #在内存中建立一个doc文档
  • Demo.add_paragraph(data) #在doc文档中添加一个段落
  • Demo.add_picture(“pic.png”) #doc文档中添加一个图片
  • Demo.save(‘demo.docx’) #存储docx文档

观察html结构:

五年Python爬虫程序员整理的全栈爬虫知识点

我们大致观察一下结构,定位到文章的具体内容需要找到标签,然后再遍历标签的子标签即可遍历到所有的段落,配图资料

五年Python爬虫程序员整理的全栈爬虫知识点

这样定位到图片,那么我们怎么样来寻找

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">from docx import Document
from bs4 import BeautifulSoup
import urllib
url ="http://freebuf.com/news/94263.html"
data = urllib.urlopen(url)
document = Document()
soup = BeautifulSoup(data)
article = soup.find(name ="div",attrs={'id':'contenttxt'}).children

这段是提取我们所要的信息

for e in article:
try:
if e.img:
pic_name = ''
print e.img.attrs['src']
if 'gif' in e.img.attrs['src']:
pic_name = 'temp.gif'
elif 'png' in e.img.attrs['src']:
pic_name = 'temp.png'
elif 'jpg' in e.img.attrs['src']:
pic_name = 'temp.jpg'
else:
pic_name = 'temp.jpeg'
urllib.urlretrieve(e.img.attrs['src'], filename=pic_name)

下面我们再来看看 urllib 模块提供的 urlretrieve() 函数。urlretrieve() 方法直接将远程数据下载到本地。

1

help(urllib.urlretrieve)

2

Help on function urlretrieve in module urllib:

3

4

urlretrieve(url, filename=None, reporthook=None, data=None)

参数 finename 指定了保存本地路径(如果参数未指定,urllib会生成一个临时文件保存数据。)

参数 reporthook 是一个回调函数,当连接上服务器、以及相应的数据块传输完毕时会触发该回调,我们可以利用这个回调函数来显示当前的下载进度。

参数 data 指 post 到服务器的数据,该方法返回一个包含两个元素的(filename, headers)元组,filename 表示保存到本地的路径,header 表示服务器的响应头。

document.add_picture(pic_name)
except:
pass
if e.string:
print e.string.encode('gbk','ignore')
document.add_paragraph(e.string)
document.save("freebuf_article.docx")
print "success create a document"</pre>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容