Python 爬虫基础

网络参数

1.header下
(1)user-agent:这个是爬虫header里可以说必须的东西,基本网站肯定第一个都是判断这个
(2)referer:代表你是从哪来访问到这个界面的,也是比较经常拿来判断是否为爬虫的参数
(3)accept-encoding:代表用什么进行数据压缩,有时候如果压缩了数据会变化,甚至乱码,这时候可能header里面删掉这个比较好
(4)cookie:这个也是非常重要的内容,但经常都是有时间限制
2.data
(1)saltjs加密一般都是在服务器对数据加密后传输,而加密的内容可能原来就几位数,如果准备了一个字典进行暴力破解有时候也不是难事,为了应对这种情况,有时候就会用salt,也就是一大串很长的字符串,和加密结合,从而做到一定程度的反爬虫
(2)sign:签名,也是一种安全认证机制,和salt一样一般加密方法都写在js里,此时去js找到后翻译成python代码模拟生成其内容,js代码不懂什么意思可以在浏览器中按f12进入console中运行

接入网络

导入urllib包,里面包括4个模块——requesterrorparserobotparser,连接要用的就是request模块

urllib下模块详解

1.request
负责一些网络连接的请求
(1)urlopen(url, data=none, [timeout,])
用此函数连接,第一个参数是网址,第二个参数默认none,说明GET方法,如果赋值了就是以input的方式提交,举例:

import urllib.request
response = urllib.request.urlopen("http://www.whatismyip.com.tw")
#连接网页,并保存到response,该网页代码少,不会卡...
html = response.read()      #读取页面内容
html = html.decode("utf-8")     #将编码改成utf-8
print(html)     #输出内容,即网页源代码

(2)geturl()
返回你连接的url
(3)info()
返回远程服务器的header信息
(4)getcode()
返回http状态信息,比如200说明没问题,404是not found等等
(5)urlretrieve(url, filename=None, reporthook=None, data=None)
直接将远程数据下载到本地,url表示你要下载的东西地址(这个地址打开得是一个文件,比如图片、压缩包之类的,如果只是个页面就下载不了),finename表示你要给下载的文件取啥名字,reporthook是一个回调函数,可以显示当前的下载进度,datapost到服务器的数据,该方法返回一个包含两个元素的(filename,headers)元组,filename表示保存到本地的路径,header表示服务器的响应头,举例:

for each in jpg:
    filename = each.split('/')[-1]
    #这个each是一个类似:https://imgsa.baidu.com/forum/w%3D580/sign=xxx.jpg的图片网址
    #filename就是从最后一个'/'开始到后面的文件名
    urllib.request.urlretrieve(each, filename, None)    #下载该图片并取名

2.parse
对数据的处理
(1)urlencode()
对提交的数据进行转码,举例:

data  = urllib.parse.urlencode(data).encode("utf-8")  #encode就是unicode文件转成utf-8编码形式
response = urllib.request.urlopen(url, data)  #把utf-8解码成unicode形式

注:
encode函数是将一个unicode类按照指定的编码(如果不指定则使用defaultencoding)转换为不带编码标记的str类,即byte字节流;decode函数是将一个str类按照指定编码(如果不指定则使用defaultencoding)转换为使用utf-8编码的unicode
3.chardet
自动检测编码,举例:

import chardet
response = urllib.request.urlopen(url)
html = response.read()
cs = chardet.detect(html)
print(cs)  #输出一个字典,里面记录了该网页的编码、语言等(但也可能会有错)
html = html.decode(cs.get("encoding", 'utf-8"))
#将页面按chardet解析的编码解码,如果不行就用第二个参数utf-8解码
print(html)  #解码后的页面

实战

1.隐藏
网页有时会根据User-Agent来判断是否为正常用户访问,从而将非用户访问屏蔽,所以使用爬虫时要仿照浏览器信息来隐藏自己的爬虫身份,具体做法:
(1)Request对象生成前新建一个head参数(需为字典),里面放入User-Agent信息传入(referer有时也挺需要的,当然参数能尽量都传会更真实点),并且request要采用request.Request(url, data, head)方法,举例:

head = {}
head['User-Agent'] = '浏览器身份'
req = urllib.request.Request(url, data, head)   #连接前添加head
response = urllib.request.urlopen(req)

(2)request对象生成后通过Request.add_header('key','val')方法修改,举例:

req = urllib.request.Request(url, data) #连接前不用head
req.add_header('User-Agent','浏览器身份')    #添加head
response = urllib.request.urlopen(req)

2.爬取真人化
当使用爬虫时无时无刻都是爬取数据,这种非常人的行为会引起怀疑,所以需要使用设置阈值来进行周期控制或者设置代理
(1)设置阈值
引入time模块,使用sleep()方法控制单次时间,举例:

time.sleep(2)   #延迟2秒后运行

(2)代理
利用多个代理同时爬取(比如去:http://cn-proxy.com/找,需要翻墙),步骤:第一步设置一个字典包含代理服务器,格式:{'协议':'ip:port'},然后将字典放入ProxyHandler()方法内;第二步用build_opener()方法定制、创建一个opener,并将代理放入;第三步用install_opener()方法安装opener,当然前面这个方法是全程只使用这个opener,如果想只在需要时使用可以用opener.open()方法,并把代理链接放入,举例:

proxy_support=urllib.request.ProxyHandler({'http':'39.134.10.11:8080'})
opener = urllib.request.build_opener(proxy_support)
#print(proxy_support.proxies)   #想看自己用的代理ip时用这个可以看
opener.addheaders = [('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36')]
#加入header,可选
urllib.request.install_opener(opener)

注:
可以同时设置多个代理,设置一个列表,然后将列表放入ProxyHandler()方法里,举例:

iplist=['221.130.253.135:8090','39.134.10.11:8080','112.21.164.58:1080']    #代理ip列表
proxy_support=urllib.request.ProxyHandler({'http':random.choice(iplist)})   #随机选取一个ip

3.不在html上的内容
当要爬取的数据不在源代码上,说明可能是以json数据传输,所以如果要截获它,首先在浏览器下按f12,选择network下,刷新后会发现是很多文件加载组成的这个网页,我们需要的数据都在这里面,比如网易云评论。那么多文件一个个找显然不合理,所以可以通过调慢网速然后等一个个文件加载,当有我们想要的时候就暂停,然后找到这个文件,后面就好做了,这里步骤就是:
(1)勾上Disable cache,网速调慢比如regular 2G(在Disable cache右边应该),然后左上边有个红色的按钮记住
(2)刷新网页,当我们想要的东西出现时停止刷新(把刷新的×点了),然后也点前面那个红色的按钮,剩下的东西也就会停止加载了,这个时候基本要的东西也出来了,就可以找了
(3)在XHRDoc文件类型中找找,一般数据都传这里面(看previewresponse也就是整理好的样式),找到以后就可以看它请求的url了,然后的一些data数据啥的拷下来,接下来看他的post还是get方法,然后传相应数据就可以了
(4)如果数据保存在json当中,可以通过导入json模块,并使用json.loads(res.text)方法来读取,此时内容会被保存为字典形式,提取时就可以按dict[]方式了

异常处理

需要先导入urllib.error

Http返回状态码
100——299    代表成功
400——499    代表客户端问题导致的连接失败
500——599    代表服务端出问题导致连接失败

更多可以导入requests,然后输入requests.codes,然后看源代码。

异常写法一
try:
    response = urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
    print(e.code)       #HTTP输出code
except urllib.error.URLError as e:  #记住这种写法要把HTTP的写在URL的前面
    print(e.reason)     #URL输出reason
else:
    #normal situlation
异常写法二(推荐)
try:
    response = urllib.request.urlopen(req)
except urllib.error.URLError as e:
    if hasattr(e, 'reason'):
        print(e.reason)
    elif hasattr(e, 'code'):
        print(e.code)
else:
    #normal situlation

更好的第三方库

python里有更好的模块用于爬虫,忘了上面自带的,通过:pip install安装requests模块和bs4模块,这两个模块的使用很简单,最主要有两个方法:requests.get()bs4.BeautifulSoup(),第一个负责连接网页并获取内容,第二个方法能把内容转码并保存在容器中

1.requests模块

(1)get(url[,param=payload,headers=headers...])
get方法连接网页,可选是否传参和引入报头,如果要传参或引入报头,传入内容需为字典形式,当证书有问题时,比如12306不是使用ssl协议,所以用https登录会提示不可信的链接,即报错,这时候在get参数里加上一个:verify=False就可以了,举例:

requests.get(url, headers=header, verify=False)

不过这时候虽然能连接,但可能还是会有警告,所以在连接前下面这句捕捉警告就可以了:

logging.captureWarnings(True)

注:
get方法里可以传参数方法:
requests.get('url?key1=value1&key2=value2&...')
requests.get(url, params={'key1':'value1', 'key2':'value2', ...})
(2)post(url[,data=data,headers=headers...])
post方法连接网页,使用方法参考get
注:
requests连接返回的是一个response类型,直接打印是连接状态码(成功为200),其内容(即response.text)为str类型,上面两个连接方法可以传入的参数很多,不只是参数、报头,还有:

代理          proxies=proxies,其中proxies格式:{"http":"ip:port"}
cookies      cookies=cookies,可以登录后把cookie信息放到header里然后传进去
请求时间      timeout=几秒
json数据      json=payload
文件          files=files
data

(3)encoding
查看获取文本的编码,举例:

res = requests.get(url)
res.encoding
#结果输出编码格式,如果要修改输出编码格式就给这个赋值,例如:res.encoding = 'gb2312'

(4)text
查看获取的页面内容,一般是源代码之类的文本信息
(5)content
也是获取的页面内容,但这个返回的是字节流,如果是要下载图片、视频或者网页(整个网页而不只是源代码)之类的就用这个,比如下载一张网上的图片:

with open('a.jpg','wb') as f:
    f.write(res.content)

(6)status_code
返回访问页面的状态码(一般200是访问成功的状态码),我们可以用这个来判断页面是否访问成功(raise_for_status()抛出失败请求异常),举例:

if res.status_code == 200:
    print("success")

(7)headers
以字典形式返回报头,里面有:Content-Typeserverconnection等很多内容,比如我只想知道是啥内容类型可以:res.headers['Content-Type']或者res.headers.get('Content-Type')
(8)cookies
返回cookies
(9)history
可以返回是否内容被重定向,比如原来是http变成https
(10)url
可以返回你访问的网页的网址,比如你访问"http://www.baidu.com",但是肯定会变成"https://www.baidu.com",所以这时候res.url就会返回后一个网址
注:
(3)——(10)都是在连接后的对象里进行的操作
(11)Session()
会话维持,可以模拟登陆,然后就共享这一个会话,像如果两次访问一个界面,会话是不同的,所以cookies也是不一样,比如:

data = {'name':'aaa','password':'111'}
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
r = requests.post("http://localhost:8080/BookShop/Servlet",data=data, headers=headers)  #访问1
s = requests.get('http://localhost:8080/BookShop/Servlet')  #访问2,get没有cookie
print(r.cookies)    #因为2次访问会话不同,可以发现cookies也不一样
print(s.cookies)

结果:
<RequestsCookieJar[<Cookie JSESSIONID=2E70361488F66F167EB148A6823C396C for localhost.local/BookShop>]>
<RequestsCookieJar[]>

而用Session()以后,这几个访问都在同一个会话当中,cookies也是相同的,举例:

import requests
s = requests.Session()      #创建一个会话
data = {'name':'aaa','password':'111'}
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
s.get('http://localhost:8080/BookShop/Servlet')     #先访问这个页面获得会话,但并没有cookie
r = s.post("http://localhost:8080/BookShop/Servlet",data=data, headers=headers) #这次访问还是跟上次一样的会话
print(r.cookies)        #会发现两个都有cookies,且值相同
print(s.cookies)

结果为:
<RequestsCookieJar[<Cookie JSESSIONID=101406D2016C041539F2BD40CE47696E for localhost.local/BookShop>]>
<RequestsCookieJar[<Cookie JSESSIONID=101406D2016C041539F2BD40CE47696E for localhost.local/BookShop>]>

(12)json()
如果响应的结果是个json格式的内容,可以直接用requests.json()保存下来,比如:

res = requests.get('http://www.httpbin.org/get')    #该页面访问结果是一个json数据
print(res.text)
print(res.json())   #以json格式保存

(13)codes
输入requests.codes,查看源代码可以看到所有状态码对应的意义,并且从其集合里也可以找到对应的代替这个状态码的字符,比如200的集合里有ok,所以可以用requests.codes.ok代替200,更多可以看源代码里写的(比如requests.codes.not_found==404),这里挖一部分源代码:

# Informational.
100: ('continue',)
200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓')

# Redirection.
302: ('found',)
305: ('use_proxy',)
306: ('switch_proxy',)

# Client Error.
400: ('bad_request', 'bad')
403: ('forbidden',)
404: ('not_found', '-o-')

# Server Error.
500: ('internal_server_error', 'server_error', '/o\\', '✗')
502: ('bad_gateway',)
504: ('gateway_timeout',)
异常

requests下异常导入:from requests.exceptions import RequestException,然后就可以直接用了:

try:
    res = request.get(url)
    …
except RequestException:
    …
更多参考

https://blog.csdn.net/imail2016/article/details/72381989

官方中文文档

http://cn.python-requests.org/zh_CN/latest/

2. BeautifulSoup4模块

(导入时import bs4
(1)BeautifulSoup(获取的内容,解析器)
比如用res = requests.get()获取页面后可以通过这个方法获取内容,如果要源代码文本,就第一个参数是:res.text,要流文件就:res.content,参考上面requests模块可以获取的内容,第二个解析器默认的话用html.parser,用lxml也可以,如果是xml语言,则解析器用xml,但后面两个解析器需要安装C语言库,使用举例:

soup = bs4.BeautifulSoup(res.text, 'html.parser')

(2)soup.target
可以直接通过:soup.标签来获取内容,比如:print(soup.title)就可以知道title标签里的内容了(不过标签也会附上,如果只要内容,就在后面再加.string),但比如有很多个p标签,那么:soup.p就只返回第一个p标签的内容,我们还可以获取标签的属性值,比如:print(soup.img['name']),但要注意的是因为只能获取第一个标签的信息,所以第一个标签如果没有这个属性就会报错,比如上面这个img的,如果第二个img有name属性,第一个没有,那么就会报错,为了获得的第一个标签是我们想要的,我们可以嵌套着获取标签,比如:

print(soup.head.title.string)

还有想要知道子标签可以用:contentchildren,比如要知道p标签下所有标签:soup.p.content;要知道父标签就parent,如果要知道所有祖先标签,即父标签、父父标签、所有他上级的标签的话用parents;要知道兄弟标签则next_siblings
(3)soup.prettify()
格式化代码,输出时会好看些
(4)find_all(['标签名',属性='值'])
可以从这源代码当中获取标签,比如获取所有的<a href=''></a>即<a>标签可以:

targets = soup.find_all('a')

如果还想获得的a标签的class属性为abc,则:

targets = soup.find_all('a',class_='abc')

class在python中作为类的关键字,所以要查找网页中的class标签时应该在后面加下划线,即class_,也可以不根据标签而是满足某个属性:

targets = soup.find_all(class_="BDE_Smiley")

并且该方法还可以以集合或者字典形式传入多个参数,比如获取多个标签:

targets = soup.find_all({"img", "title", "head"})

获取多种条件的一个标签:

targets = soup.find_all("img", {"class":{"BDE_Image","BDE_Smiley"}})

还有就是必须用集合形式来传参的情况,比如有些有标签属性带-,而此时就会报错(只允许有_和字母数字),举例:

ip_s = soup.find_all('td', attrs={'data-title':'IP'})
#data-title带-,不能写成data-title='IP'

注:
find_all返回的是个结果集,所以targets类型是结果集,targets下内容是标签(比如for each in targets中的each是标签),标签下的text之类的属性才是str;而结果集下没有text属性,标签下才有
注2:
返回的结果集下假如有多个<a>标签,那么索引(targets.a)只能返回第一个<a>标签的结果,此时如果想要所有的<a>标签,可以在下面继续用find_all()嵌套索引,举例:targets.find_all('a')
(5)获取内容
接着(2),当取得targets后因为内容是一大堆<a>标签和对应内容,如果我们想要<a>的内容的话就:

for each in targets:
    print(each.text)

如果想要某个属性,比如src的话就可以:

for each in targets:
    print(each.get("src"))

(6)select('查找内容')
①如果是查找标签,直接标签名就行,例如:soup.select('title')
②如果是查找id则前面加#,即soup.select('#id名')
③如果查找class则前面加.,例如:soup.select('.List-header')
④如果要获取标签某个属性值则到标签下加['属性名']就好了,例如:soup.select('a')[0]['href'],则获取第一个a标签下的href
⑤如果要层层迭代,就空格隔开,比如要获得id为abc下被div修饰的p标签:soup.select('#abc div p')
注:
最终返回的是个列表,列表下是标签,标签下是str

综合简单示例
import requests
import bs4

url = "https://movie.douban.com/top250"
header = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
res = requests.get(url, headers=header)  #引入报头,如果get方法请求参数:params=payload
soup = bs4.BeautifulSoup(res.text, "html.parser")
#将res连接得到的内容转文本形式给soup,解析器用默认的parser 
targets = soup.find_all("div", class_="hd")
#找到想定位的地方,这里是一个div标签,class属性值是hd
#因为class在python中是类,所以非类的class用class_代替
for each in targets:
    print(each.a.span.text)  #输出div标签下的a标签的span标签里的文本内容

3.pyquery

其和bs4功能差不多,同作为CSS解析器,而且其语法几乎和jquery相同,所以有时候更为方便

4.requests_html

是requests模块的作者又开发出来的一个新模块,相当于requestsbs4的结合,并且还多了JS编译等功能,详细可以参考:
https://segmentfault.com/a/1190000015641160

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

推荐阅读更多精彩内容

  • 爬虫是什么爬虫是一段用来抓取互联网数据的一段程序,给定一个位置(url)为起点,爬虫从这个url开始,爬去互联网上...
    zhile_doing阅读 681评论 0 0
  • Python非常适合用来开发网页爬虫,理由如下:1、抓取网页本身的接口相比与其他静态编程语言,如java,c#,c...
    志者不俗阅读 256评论 0 0
  • 好像Python从流行起来,她就与爬虫有着千丝万缕的关系。一提到Python就想到爬虫程序,可能是因为Python...
    罗蓁蓁阅读 650评论 0 4
  • Python爬虫-Urllib方式 - 前言 此次我将讲述Python爬虫urllib与requests访问方式的...
    黑羊的皇冠阅读 11,611评论 4 13
  • 本文简单介绍了requests的基本使用,python爬虫中requests模块绝对是是最好用的网络请求模块,可以...
    迢晴阅读 1,290评论 0 1