如何制作一个爬虫
参考链接:
https://cuiqingcai.com/5052.html
环境与库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 请求库 Requests Selenium ChromeDriver GeckoDriver PhantomJS aiohttp 解析库 lxml Beautiful Soup pyquery tesserocr 数据库 MySQL MongoDB Redis 存储库 PyMySQL PyMongo redis-py RedisDump Web 库 Flask Tornado App 爬取相关库 Charles mitmproxy Appium 爬虫框架 pyspider Scrapy Scrapy-Splash Scrapy-Redis 部署相关库 Docker Scrapyd Scrapyd-Client Scrapyd API Scrapyrt Gerapy
其中我这边使用的库是:Beautiful Soup ,Requests ,Selenium ,ChromeDriver 等
其他的库根据不同场景的需要进行处理即可
Robots 协议
参考链接:
创建并提交 robots.txt 文件 | Google 搜索中心 | 文档 | Google for Developers
Robots 协议也称作爬虫协议、机器人协议,它的全名叫作网络爬虫排除标准(Robots Exclusion Protocol),用来告诉爬虫和搜索引擎哪些页面可以抓取,哪些不可以抓取
对于该协议,如果存在,搜索爬虫会根据其中定义的爬取范围来爬取。如果没有找到这个文件,搜索爬虫便会访问所有可直接访问的页面。(当然,也有很多人不会管这些)
example
1 2 3 User-agent: * Disallow: / Allow: /public/
实用的 robots.txt 规则
实用规则
禁止抓取整个网站
请注意,在某些情况下,Google 即使未抓取网站中的网址,仍可能将其编入索引。注意 :这不适用于各种 AdsBot 抓取工具 ,此类抓取工具必须明确指定。User-agent: * Disallow: /
禁止抓取某一目录及其内容
在目录名后添加一道正斜线,即可禁止抓取整个目录。注意 :请勿使用 robots.txt 禁止访问私密内容;请改用正确的身份验证机制。对于 robots.txt 文件所禁止抓取的网址,Google 仍可能会在不进行抓取的情况下将其编入索引;另外,由于 robots.txt 文件可供任何人随意查看,因此可能会泄露您的私密内容的位置。User-agent: * Disallow: /calendar/ Disallow: /junk/ Disallow: /books/fiction/contemporary/
仅允许某一抓取工具访问网站内容
只有 googlebot-news
可以抓取整个网站。User-agent: Googlebot-news Allow: / User-agent: * Disallow: /
允许除某一抓取工具以外的其他所有抓取工具访问网站内容
Unnecessarybot
不能抓取相应网站,所有其他漫游器都可以。User-agent: Unnecessarybot Disallow: / User-agent: * Allow: /
禁止抓取某一网页
例如,禁止抓取位于 https://example.com/useless_file.html
的 useless_file.html
页面和 junk
目录中的 other_useless_file.html
。User-agent: * Disallow: /useless_file.html Disallow: /junk/other_useless_file.html
禁止抓取除子目录以外的整个网站
抓取工具只能访问 public
子目录。User-agent: * Disallow: / Allow: /public/
禁止 Google 图片访问某一特定图片
例如,禁止访问 dogs.jpg
图片。User-agent: Googlebot-Image Disallow: /images/dogs.jpg
禁止 Google 图片访问您网站上的所有图片
如果无法抓取图片和视频,则 Google 无法将其编入索引。User-agent: Googlebot-Image Disallow: /
禁止抓取某一特定文件类型的文件
例如,禁止抓取所有 .gif
文件。User-agent: Googlebot Disallow: /*.gif$
禁止抓取整个网站,但允许 Mediapartners-Google
访问内容
实施此规则会阻止您的网页显示在搜索结果中,但 Mediapartners-Google
网页抓取工具仍能分析这些网页,以确定要向访问您网站的用户显示哪些广告。User-agent: * Disallow: / User-agent: Mediapartners-Google Allow: /
使用 *
和 $
通配符匹配以特定字符串结尾的网址
例如,禁止抓取所有 .xls
文件。User-agent: Googlebot Disallow: /*.xls$
robotparser
用于自动分析路径是否可以爬取
1 2 3 4 5 6 7 from urllib.robotparser import RobotFileParserrp = RobotFileParser() rp.set_url('http://www.jianshu.com/robots.txt' ) rp.read() print (rp.can_fetch('*' , 'http://www.jianshu.com/p/b67554025d7d' ))print (rp.can_fetch('*' , "http://www.jianshu.com/search?q=python&page=1&type=collections" ))
requests
对于一些基础的用法不再介绍,例如get,post请求,cookie设置,header的设置,状态码的判断等都自行学习,主要聊一下高级用法,对于爬虫的一些各种场景的绕过
文件上传
1 2 3 4 5 import requestsfiles = {'file' : open ('favicon.ico' , 'rb' )} r = requests.post("http://httpbin.org/post" , files=files) print (r.text)
Cookie以及会话
1 2 3 4 5 6 import requestsr = requests.get("https://www.baidu.com" ) print (r.cookies)for key, value in r.cookies.items(): print (key + '=' + value)
1 2 3 4 5 6 import requestss = requests.Session() s.get('http://httpbin.org/cookies/set/number/123456789' ) r = s.get('http://httpbin.org/cookies' ) print (r.text)
采用session的时候,相当于我们维持在一个网页上面
SSL证书验证
如果请求一个 HTTPS 站点,但是证书验证错误的页面时,就会报这样的错误,那么如何避免这个错误呢?很简单,把 verify
参数设置为 False
即可。相关代码如下:
1 2 3 4 import requestsresponse = requests.get('https://test.cn' , verify=False ) print (response.status_code)
不过我们发现报了一个警告,它建议我们给它指定证书。我们可以通过设置忽略警告的方式来屏蔽这个警告:
1 2 3 4 5 6 import requestsfrom requests.packages import urllib3urllib3.disable_warnings() response = requests.get('https://test.cn' , verify=False ) print (response.status_code)
或者通过捕获警告到日志的方式忽略警告:
1 2 3 4 5 import loggingimport requestslogging.captureWarnings(True ) response = requests.get('https://test.cn' , verify=False ) print (response.status_code)
当然,我们也可以指定一个本地证书用作客户端证书,这可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组:
1 2 3 4 import requestsresponse = requests.get('https://www.12306.cn' , cert=('/path/server.crt' , '/path/key' )) print (response.status_code)
代理设置
1 2 3 4 5 6 7 8 import requestsproxies = { "http" : "http://user:password@10.10.1.10:3128/" , 'http' : 'socks5://user:password@host:port' , 'https' : 'socks5://user:password@host:port' } requests.get("https://www.taobao.com" , proxies=proxies)
身份认证
在访问网站时,我们可能会遇到这样的认证页面
此时可以使用 requests 自带的身份认证功能
1 2 3 4 import requestsr = requests.get('http://localhost:5000' , auth=('username' , 'password' )) print (r.status_code)
如果需要OA认证
1 pip3 install requests_oauthlib
使用 OAuth1 认证的方法如下:
1 2 3 4 5 6 7 import requestsfrom requests_oauthlib import OAuth1url = 'https://api.twitter.com/1.1/account/verify_credentials.json' auth = OAuth1('YOUR_APP_KEY' , 'YOUR_APP_SECRET' , 'USER_OAUTH_TOKEN' , 'USER_OAUTH_TOKEN_SECRET' ) requests.get(url, auth=auth)
Prepared Request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from requests import Request, Sessionurl = 'http://httpbin.org/post' data = { 'name' : 'germey' } headers = { 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36' } s = Session() req = Request('POST' , url, data=data, headers=headers) prepped = s.prepare_request(req) r = s.send(prepped) print (r.text)
这里我们引入了 Request
,然后用 url
、data
和 headers
参数构造了一个 Request
对象,这时需要再调用 Session
的 prepare_request()
方法将其转换为一个 Prepared Request 对象,然后调用 send()
方法发送即可,运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "args": {}, "data": "", "files": {}, "form": { "name": "germey" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "11", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36" }, "json": null, "origin": "182.32.203.166", "url": "http://httpbin.org/post" }
可以看到,我们达到了同样的 POST 请求效果。
有了 Request
这个对象,就可以将请求当作独立的对象来看待,这样在进行队列调度时会非常方便。后面我们会用它来构造一个 Request
队列。
解析html
任选一个就行
lxml
xpath语法
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from lxml import etreetext = ''' <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a> </ul> </div> ''' html = etree.HTML(text) li_elements = html.xpath("//li" ) for li in li_elements: print (etree.tostring(li, encoding='unicode' ))
1 2 3 4 5 from lxml import etreehtml = etree.parse('./test.html' , etree.HTMLParser()) result = etree.tostring(html) print (result.decode('utf-8' ))
1 2 3 4 5 6 7 8 result = html.xpath('//*' ) result = html.xpath('//li/a' ) result = html.xpath('//ul//a' ) result = html.xpath('//a[@href="test"]/../@class' ) result = html.xpath('//a[@href="test"]/parent::*/@class' ) result = html.xpath('//li/a/test()' ) result = html.xpath('//li[contains(@class, "li")]/a/text()' )
Beautiful Soup
基础解析
1 2 soup = BeautifulSoup('<p>Hello</p>' , 'lxml' ) soup = BeautifulSoup('<p>Hello</p>' , 'html.parser' )
之前我是一直使用的是html.parser,来学一下lxml
1 2 3 4 5 6 7 8 9 10 11 12 13 soup = BeautifulSoup(html, 'lxml' ) print (soup.prettify()) print (soup.p.string) print (soup.p.attrs) print (soup.p['name' ]) print (soup.p.children) print (soup.p.descendants) print (soup.a.parent) print (soup.a.parents) print (soup.a.next_sibling) print (soup.a.previous_sibling) for i, child in enumerate (soup.p.children): print (i, child)
方法选择器,直接得到特定条件的节点
1 2 3 4 5 6 print (soup.find_all(name='ul' ))find_parents() 和 find_parent():前者返回所有祖先节点,后者返回直接父节点。 find_next_siblings() 和 find_next_sibling():前者返回后面所有的兄弟节点,后者返回后面第一个兄弟节点。 find_previous_siblings() 和 find_previous_sibling():前者返回前面所有的兄弟节点,后者返回前面第一个兄弟节点。 find_all_next() 和 find_next():前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。 find_all_previous() 和 find_previous():前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。
css 选择器,获取div标签下面的节点
1 2 3 print (soup.select('.panel .panel-heading' )) print (soup.select('ul li' )) print (soup.select('#list-2 .element' ))
实战
Ajax
对于前后端进行数据交互之后在进行渲染的情况,存在下面的方法对数据进行爬取
首先Ajax的交互方式,前端发包情况隶属于XHR的发包,可以在F12之后看到,具体方法可以查看下面的文章:
https://cuiqingcai.com/5597.html
实战可以查看:
https://cuiqingcai.com/5609.html
但是对于这样的实战手法,仅仅考虑明文传输,假设出现密文传输的方式,在安全的领域下面需要进行前端的逆向操作,