模拟登陆与文件下载
爬取http://moodle.tipdm.com上面的视频并下载
模拟登陆
由于泰迪杯网站问题,测试之后发现无法用正常的账号密码登陆,这里会使用访客账号登陆。
我们先打开泰迪杯的登陆界面,打开开发者工具,选择Network选单,点击访客登陆。
注意到index.php的资源请求是一个POST请求,我们把视窗拉倒最下面,看到表单数据(Form data),浏览器在表单数据中发送了两个变量,分别是username和password,两个变量的值都是guest。这就是我们需要告诉网站的信息了。
知道了这些信息,我们就可以使用requesst来模拟登陆了。
import requests
s = requests.Session()
data = {
'username': 'guest',
'password': 'guest',
}
r = s.post('http://moodle.tipdm.com/login/index.php', data)
print(r.url)
我们引入requests包。但我们这次并没有直接使用request.post(),而是在第二行先创建了一个Session实例s,Session实例可以将浏览过程中的cookies保存下来。cookies指网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。泰迪网站要聪明一点,就不是只用GET请求传递的数据来确认用户身份,而是要用保存在本地的cookies来确认用户身份(你再也不能伪装成隔壁老王了)。在requests中我们只要创建一个Session实例(比如这里的s),然后之后的请求都用Session实例s来发送,cookies的事情就不用管了。s.post()是用来发送POST请求的(requests.get()发送GET请求)。同理s.get()是用来发送get请求的。POST请求是一定要向服务器发送一个表单数据(form data)的。
我们看到泰迪网站要求上传的表单数据就是username和password,两者的值都是guest,所以在python里面我们创建一个dict,命名为data,里面的数据就输入username和password。最后再用s把数据post到网址,模拟登陆就完成了。
我们运行一下代码,
登陆成功了可以看到网址跳转到了泰迪教程的首页,和在浏览器里面的行为是一样的。这样我吗就实现了模拟登陆一个网站。
视频下载
我们进入到我们要下载的视频的页面(http://moodle.tipdm.com/course/view.php?id=16),然后对要下载的链接进行审查元素。
元素都在`a`标签中,所有这样的a标签(a tag)都在<div class="activityinstance">标签中。所以我们只要找到所有的class为acticityinstance的div标签,然后提取里面a标签的href属性,就知道视频的地址了。同样的,我们使用beautiful soup包来实现我们想要的功能。
from bs4 import BeautifulSoup
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs:
url = div.a.get('href')
print(url)
现在所有的代码看起来应该是这样的:
import requests
from bs4 import BeautifulSoup
data = {
'username': 'guest',
'password': 'guest',
}
s = requests.Session()
r = s.post('http://moodle.tipdm.com/login/index.php', data)
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs:
url = div.a.get('href')
print(url)
运行一下,
你已经拿到了所有的网址我们点开其中的一个网址,看看里面的结构:
可以看到下载链接已经在你面前了,我们对它进行审查元素,看到了一个.mp4的下载地址,那下一步我们就是要获取这个mp4的下载地址。
for div in divs[1:]: # 注意这里也出现了改动
url = div.a.get('href')
r = s.get(url)
soup = BeautifulSoup(r.text, 'lxml')
target_div = soup.find('div', class_='resourceworkaround')
target_url = target_div.a.get('href')
print(target_url)
divs[1:]的意思是我们忽视掉divs列表(list)中的第一个元素,然后进行下面的操作。
到目前为止,你的代码看起来应该是这样的:
import requests
from bs4 import BeautifulSoup
data = {
'username': 'guest',
'password': 'guest',
}
s = requests.Session()
r = s.post('http://moodle.tipdm.com/login/index.php', data)
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs[1:]: # 注意这里也出现了改动
url = div.a.get('href')
r = s.get(url)
soup = BeautifulSoup(r.text, 'lxml')
target_div = soup.find('div', class_='resourceworkaround')
target_url = target_div.a.get('href')
print(target_url)
运行一下代码:
恭喜你,你成功获取到了视频的下载地址。现在将我在这里提供的代码复制到你的代码前面:
def download(url, s):
import urllib, os
file_name = urllib.parse.unquote(url)
file_name = file_name[file_name.rfind('/') + 1:]
try:
r = s.get(url, stream=True, timeout = 2)
chunk_size = 1000
timer = 0
length = int(r.headers['Content-Length'])
print('downloading {}'.format(file_name))
if os.path.isfile('./' + file_name):
print(' file already exist, skipped')
return
with open('./' + file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size):
timer += chunk_size
percent = round(timer/length, 4) * 100
print('\r {:4f}'.format((percent)), end = '')
f.write(chunk)
print('\r finished ')
except requests.exceptions.ReadTimeout:
print('read time out, this file failed to download')
return
except requests.exceptions.ConnectionError:
print('ConnectionError, this file failed to download')
return
然后在你循环的末尾加上download(target_url, s)
现在整个代码看起来是这样的:
import requests
from bs4 import BeautifulSoup
data = {
'username': 'guest',
'password': 'guest',
}
def download(url, s):
import urllib, os
file_name = urllib.parse.unquote(url)
file_name = file_name[file_name.rfind('/') + 1:]
try:
r = s.get(url, stream=True, timeout = 2)
chunk_size = 1000
timer = 0
length = int(r.headers['Content-Length'])
print('downloading {}'.format(file_name))
if os.path.isfile('./' + file_name):
print(' file already exist, skipped')
return
with open('./' + file_name, 'wb') as f:
for chunk in r.iter_content(chunk_size):
timer += chunk_size
percent = round(timer/length, 4) * 100
print('\r {:4f}'.format((percent)), end = '')
f.write(chunk)
print('\r finished ')
except requests.exceptions.ReadTimeout:
print('read time out, this file failed to download')
return
except requests.exceptions.ConnectionError:
print('ConnectionError, this file failed to download')
return
s = requests.Session()
r = s.post('http://moodle.tipdm.com/login/index.php', data)
r = s.get('http://moodle.tipdm.com/course/view.php?id=16')
soup = BeautifulSoup(r.text, 'lxml')
divs = soup.find_all("div", class_='activityinstance')
for div in divs[1:]:
url = div.a.get('href')
r = s.get(url)
soup = BeautifulSoup(r.text, 'lxml')
target_div = soup.find('div', class_='resourceworkaround')
target_url = target_div.a.get('href')
download(target_url, s)
运行一下,视频已经开始下载了
这样你已经成功学会了如何模拟登陆一个网站,并且学会了如何从网站上面下载一个文件。
排行榜小说批量下载
我们要爬取的是小说,排行榜的地址:http://www.qu.la/paihangbang/。先观察下网页的结构
很容易就能发现,每一个分类都是包裹在:<div class="index_toplist mright mbottom">之中,这种条理清晰的网站,大大方便了爬虫的编写。在当前页面找到所有小说的连接,并保存在列表即可。这里有个问题,就算是不同类别的小说,也是会重复出现在排行榜的。这样无形之间就会浪费爬取时间。解决方法就是:url_list = list(set(url_list)) 这里调用了一个list的构造函数set:这样就能保证列表里没有重复的元素了。
代码实现
1.网页抓取头:
import requests
from bs4 import BeautifulSoup
def get_html(url):
try:
r = requests.get(url,timeout=30)
r.raise_for_status
r.encoding='utf-8'
return r.text
except:
return 'error!'
2.获取排行榜小说及其链接:
爬取每一类型小说排行榜, 按顺序写入文件, 文件内容为:小说名字+小说链接 将内容保存到列表并且返回一个装满url链接的列表
def get_content(url):
url_list = []
html = get_html(url)
soup = BeautifulSoup(html,'lxml')
# 由于小说排版的原因,历史类和完本类小说不在一个div里
category_list = soup.find_all('div',class_='index_toplist mright mbottom')
history_list = soup.find_all('div',class_='index_toplist mbottom')
for cate in category_list:
name = cate.find('div',class_='toptab').span.text
with open('novel_list.csv','a+') as f:
f.write('\n小说种类:{} \n'.format(name))
book_list = cate.find('div',class_='topbooks').find_all('li')
# 循环遍历出每一个小说的的名字,以及链接
for book in book_list:
link = 'http://www.qu.la/' + book.a['href']
title = book.a['title']
url_list.append(link)
# 这里使用a模式写入,防止清空文件
with open('novel_list.csv','a') as f:
f.write('小说名:{} \t 小说地址:{} \n'.format(title,link))
for cate in history_list:
name = cate.find('div',class_='toptab').span.text
with open('novel_list.csv','a') as f:
f.write('\n小说种类: {} \n'.format(name))
book_list = cate.find('div',class_='topbooks').find_all('li')
for book in book_list:
link = 'http://www.qu.la/' + book.a['href']
title = book.a['title']
url_list.append(link)
with open('novel_list.csv','a') as f:
f.write('小说名:{} \t 小说地址:{} \n'.format(title,link))
return url_list
3.获取单本小说的所有章节链接:
获取该小说每个章节的url地址,并创建小说文件
# 获取单本小说的所有章节链接
def get_txt_url(url):
url_list = []
html = get_html(url)
soup = BeautifulSoup(html,'lxml')
list_a = soup.find_all('dd')
txt_name = soup.find('dt').text
with open('C:/Users/Administrator/Desktop/小说/{}.txt'.format(txt_name),'a+') as f:
f.write('小说标题:{} \n'.format(txt_name))
for url in list_a:
url_list.append('http://www.qu.la/' + url.a['href'])
return url_list,txt_name
4.获取单页文章的内容并保存到本地
从网上爬下来的文件很多时候都是带着<br>之类的格式化标签,可以通过一个简单的方法把它过滤掉:
html = get_html(url).replace('<br/>', '\n')
这里单单过滤了一种标签,并将其替换成‘\n’用于文章的换行,
def get_one_txt(url,txt_name):
html = get_html(url).replace('<br/>','\n')
soup = BeautifulSoup(html,'lxml')
try:
txt = soup.find('div',id='content').text
title = soup.find('h1').text
with open('C:/Users/Administrator/Desktop/小说/{}.txt'.format(txt.name),'a') as f:
f.write(title + '\n\n')
f.write(txt)
print('当前小说:{}当前章节{}已经下载完毕'.format(txt_name,title))
except:
print('ERROR!')
5.主函数
def get_all_txt(url_list):
for url in url_list:
# 遍历获取当前小说的所有章节的目录,并且生成小说头文件
page_list,txt_name = get_txt_url(url)
def main():
# 小说排行榜地址
base_url = 'http://www.qu.la/paihangbang/'
# 获取排行榜中所有小说的url链接
url_list = get_content(base_url)
# 除去重复的小说
url_list = list(set(url_list))
get_all_txt(url_list)
if __name__ == '__main__':
main()
6.输出结果
多线程爬虫
最近想要抓取拉勾网的数据,最开始是使用Scrapy的,但是遇到了下面两个问题:1前端页面是用JS模板引擎生成的2接口主要是用POST提交参数的
目前不会处理使用JS模板引擎生成的HTML页面,用POST的提交参数的话,接口统一,也没有必要使用Scrapy,所以就萌生了自己写一个简单的Python爬虫的想法。该爬虫框架主要就是处理网络请求,这个简单的爬虫使用多线程来处理网络请求,使用线程来处理URL队列中的url,然后将url返回的结果保存在另一个队列中,其它线程读取这个队列中的数据,然后写到文件中去。该爬虫主要用下面几个部分组成。
1 URL队列和结果队列
将将要爬取的url放在一个队列中,这里使用标准库Queue。访问url后的结果保存在结果队列中
初始化一个URL队列
from Queue import Queue
urls_queue = Queue()
out_queue = Queue()
2 请求线程
使用多个线程,不停的取URL队列中的url,并进行处理:
import threading
class ThreadCrawl(threading.Thread):
def __init__(self, queue, out_queue):
threading.Thread.__init__(self)
self.queue = queue
self.out_queue = out_queue
def run(self):
while True:
item = self.queue.get()
self.queue.task_down()
如果队列为空,线程就会被阻塞,直到队列不为空。处理队列中的一条数据后,就需要通知队列已经处理完该条数据。
3 处理线程
处理结果队列中的数据,并保存到文件中。如果使用多个线程的话,必须要给文件加上锁。
lock = threading.Lock()
f = codecs.open('out.txt', 'w', 'utf8')
当线程需要写入文件的时候,可以这样处理:
with lock:
f.write(something)
抓取结果:
源码
代码还不完善,将会持续修改中。
from Queue import Queue
import threading
import urllib2
import time
import json
import codecs
from bs4 import BeautifulSoup
urls_queue = Queue()
data_queue = Queue()
lock = threading.Lock()
f = codecs.open('out.txt', 'w', 'utf8')
class ThreadUrl(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
pass
class ThreadCrawl(threading.Thread):
def __init__(self, url, queue, out_queue):
threading.Thread.__init__(self)
self.url = url
self.queue = queue
self.out_queue = out_queue
def run(self):
while True:
item = self.queue.get()
data = self._data_post(item)
try:
req = urllib2.Request(url=self.url, data=data)
res = urllib2.urlopen(req)
except urllib2.HTTPError, e:
raise e.reason
py_data = json.loads(res.read())
res.close()
item['first'] = 'false'
item['pn'] = item['pn'] + 1
success = py_data['success']
if success:
print 'Get success...'
else:
print 'Get fail....'
print 'pn is : %s' % item['pn']
result = py_data['content']['result']
if len(result) != 0:
self.queue.put(item)
print 'now queue size is: %d' % self.queue.qsize()
self.out_queue.put(py_data['content']['result'])
self.queue.task_done()
def _data_post(self, item):
pn = item['pn']
first = 'false'
if pn == 1:
first = 'true'
return 'first=' + first + '&pn=' + str(pn) + '&kd=' + item['kd']
def _item_queue(self):
pass
class ThreadWrite(threading.Thread):
def __init__(self, queue, lock, f):
threading.Thread.__init__(self)
self.queue = queue
self.lock = lock
self.f = f
def run(self):
while True:
item = self.queue.get()
self._parse_data(item)
self.queue.task_done()
def _parse_data(self, item):
for i in item:
l = self._item_to_str(i)
with self.lock:
print 'write %s' % l
self.f.write(l)
def _item_to_str(self, item):
positionName = item['positionName']
positionType = item['positionType']
workYear = item['workYear']
education = item['education']
jobNature = item['jobNature']
companyName = item['companyName']
companyLogo = item['companyLogo']
industryField = item['industryField']
financeStage = item['financeStage']
companyShortName = item['companyShortName']
city = item['city']
salary = item['salary']
positionFirstType = item['positionFirstType']
createTime = item['createTime']
positionId = item['positionId']
return positionName + ' ' + positionType + ' ' + workYear + ' ' + education + ' ' + \
jobNature + ' ' + companyLogo + ' ' + industryField + ' ' + financeStage + ' ' + \
companyShortName + ' ' + city + ' ' + salary + ' ' + positionFirstType + ' ' + \
createTime + ' ' + str(positionId) + '\n'
def main():
for i in range(4):
t = ThreadCrawl(
'http://www.lagou.com/jobs/positionAjax.json', urls_queue, data_queue)
t.setDaemon(True)
t.start()
datas = [
{'first': 'true', 'pn': 1, 'kd': 'Java'}
#{'first': 'true', 'pn': 1, 'kd': 'Python'}
]
for d in datas:
urls_queue.put(d)
for i in range(4):
t = ThreadWrite(data_queue, lock, f)
t.setDaemon(True)
t.start()
urls_queue.join()
data_queue.join()
with lock:
f.close()
print 'data_queue siez: %d' % data_queue.qsize()
main()
爬虫的基本流程
网络爬虫的基本工作流程如下:
●首先选取一部分精心挑选的种子URL
●将种子URL加入任务队列
●从待抓取URL队列中取出待抓取的URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL队列。
●分析已抓取URL队列中的URL,分析其中的其他URL,并且将URL放入待抓取URL队列,从而进入下一个循环。
●解析下载下来的网页,将需要的数据解析出来。
●数据持久话,保存至数据库中。
爬虫的抓取策略
在爬虫系统中,待抓取URL队列是很重要的一部分。待抓取URL队列中的URL以什么样的顺序排列也是一个很重要的问题,因为这涉及到先抓取那个页面,后抓取哪个页面。而决定这些URL排列顺序的方法,叫做抓取策略。下面重点介绍几种常见的抓取策略:
●深度优先策略(DFS) 深度优先策略是指爬虫从某个URL开始,一个链接一个链接的爬取下去,直到处理完了某个链接所在的所有线路,才切换到其它的线路。
此时抓取顺序为:A -> B -> C -> D -> E -> F -> G -> H -> I -> J
●广度优先策略(BFS) 宽度优先遍历策略的基本思路是,将新下载网页中发现的链接直接插入待抓取URL队列的末尾。也就是指网络爬虫会先抓取起始网页中链接的所有网页,然后再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页。此时抓取顺序为:A -> B -> E -> G -> H -> I -> C -> F -> J -> D
技术栈
●requests 人性化的请求发送
●Bloom Filter 布隆过滤器,用于判重
●XPath 解析HTML内容
●Anti crawler strategy 反爬虫策略
●MySQL 用户数据存储
1 调研目标网站背景
1.1 检查robots.txt
http://example.webscraping.com/robots.txt
# section 1
User-agent: BadCrawler
Disallow: /
# section 2
User-agent: *
Crawl-delay: 5
Disallow: /trap
# section 3
Sitemap: http://example.webscraping.com/sitemap.xml
●section 1 :禁止用户代理为BadCrawler的爬虫爬取该网站,除非恶意爬虫。
●section 2 :两次下载请求时间间隔5秒的爬取延迟。/trap 用于封禁恶意爬虫,会封禁1分钟不止。
●section 3 :定义一个Sitemap文件,下节讲。
1.2 检查网站地图
所有网页链接: http://example.webscraping.com/sitemap.xml
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://example.webscraping.com/view/Afghanistan-1</loc>
</url>
<url>
<loc>
http://example.webscraping.com/view/Aland-Islands-2
</loc>
</url>
...
<url>
<loc>http://example.webscraping.com/view/Zimbabwe-252</loc>
</url>
</urlset>
1.3 估算网站大小
高级搜索参数:http://www.google.com/advanced_search
Google搜索:site:http://example.webscraping.com/ 有202个网页
Google搜索:site:http://example.webscraping.com/view 有117个网页
1.4 识别网站所有技术
用buildwith模块可以检查网站构建的技术类型。
安装库:pip install buildwith
>>> import builtwith
>>> builtwith.parse('http://example.webscraping.com')
{u'javascript-frameworks': [u'jQuery', u'Modernizr', u'jQuery UI'],
u'web-frameworks': [u'Web2py', u'Twitter Bootstrap'],
u'programming-languages': [u'Python'],
u'web-servers': [u'Nginx']}
>>>
示例网址使用了Python的Web2py框架,还使用了JavaScript库,可能是嵌入在HTML中的。这种容易抓取。其他建构类型:
- AngularJS:内容动态加载
- ASP.NET:爬取网页要用到会话管理和表单提交。
基本实现
下面是一个伪代码
import Queue
initial_page = "https://www.zhihu.com/people/gaoming623"
url_queue = Queue.Queue()
seen = set()
seen.insert(initial_page)
url_queue.put(initial_page)
while(True): #一直进行
if url_queue.size()>0:
current_url = url_queue.get() #拿出队例中第一个的url
store(current_url) #把这个url代表的网页存储好
for next_url in extract_urls(current_url): #提取把这个url里链向的url
if next_url not in seen:
seen.put(next_url)
url_queue.put(next_url)
else:
break
如果你直接加工一下上面的代码直接运行的话,你需要很长的时间才能爬下整个知乎用户的信息,毕竟知乎有6000万月活跃用户。更别说Google这样的搜索引擎需要爬下全网的内容了。那么问题出现在哪里?
布隆过滤器
需要爬的网页实在太多太多了,而上面的代码太慢太慢了。设想全网有N个网站,那么分析一下判重的复杂度就是N*log(N),因为所有网页要遍历一次,而每次判重用set的话需要log(N)的复杂度。OK,我知道python的set实现是hash——不过这样还是太慢了,至少内存使用效率不高。通常的判重做法是怎样呢?Bloom Filter. 简单讲它仍然是一种hash的方法,但是它的特点是,它可以使用固定的内存(不随url的数量而增长)以O(1)的效率判定url是否已经在set中。可惜天下没有白吃的午餐,它的唯一问题在于,如果这个url不在set中,BF可以100%确定这个url没有看过。但是如果这个url在set中,它会告诉你:这个url应该已经出现过,不过我有2%的不确定性。注意这里的不确定性在你分配的内存足够大的时候,可以变得很小很少。
# bloom_filter.py
BIT_SIZE = 5000000
class BloomFilter:
def __init__(self):
# Initialize bloom filter, set size and all bits to 0
bit_array = bitarray(BIT_SIZE)
bit_array.setall(0)
self.bit_array = bit_array
def add(self, url):
# Add a url, and set points in bitarray to 1 (Points count is equal to hash funcs count.)
# Here use 7 hash functions.
point_list = self.get_postions(url)
for b in point_list:
self.bit_array[b] = 1
def contains(self, url):
# Check if a url is in a collection
point_list = self.get_postions(url)
result = True
for b in point_list:
result = result and self.bit_array[b]
return result
def get_postions(self, url):
# Get points positions in bit vector.
point1 = mmh3.hash(url, 41) % BIT_SIZE
point2 = mmh3.hash(url, 42) % BIT_SIZE
point3 = mmh3.hash(url, 43) % BIT_SIZE
point4 = mmh3.hash(url, 44) % BIT_SIZE
point5 = mmh3.hash(url, 45) % BIT_SIZE
point6 = mmh3.hash(url, 46) % BIT_SIZE
point7 = mmh3.hash(url, 47) % BIT_SIZE
return [point1, point2, point3, point4, point5, point6, point7]
建表
用户有价值的信息包括用户名、简介、行业、院校、专业及在平台上活动的数据比如回答数、文章数、提问数、粉丝数等等。
用户信息存储的表结构如下:
CREATE DATABASE `zhihu_user` /*!40100 DEFAULT CHARACTER SET utf8 */;
-- User base information table
CREATE TABLE `t_user` (
`uid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`brief_info` varchar(400) COMMENT '个人简介',
`industry` varchar(50) COMMENT '所处行业',
`education` varchar(50) COMMENT '毕业院校',
`major` varchar(50) COMMENT '主修专业',
`answer_count` int(10) unsigned DEFAULT 0 COMMENT '回答数',
`article_count` int(10) unsigned DEFAULT 0 COMMENT '文章数',
`ask_question_count` int(10) unsigned DEFAULT 0 COMMENT '提问数',
`collection_count` int(10) unsigned DEFAULT 0 COMMENT '收藏数',
`follower_count` int(10) unsigned DEFAULT 0 COMMENT '被关注数',
`followed_count` int(10) unsigned DEFAULT 0 COMMENT '关注数',
`follow_live_count` int(10) unsigned DEFAULT 0 COMMENT '关注直播数',
`follow_topic_count` int(10) unsigned DEFAULT 0 COMMENT '关注话题数',
`follow_column_count` int(10) unsigned DEFAULT 0 COMMENT '关注专栏数',
`follow_question_count` int(10) unsigned DEFAULT 0 COMMENT '关注问题数',
`follow_collection_count` int(10) unsigned DEFAULT 0 COMMENT '关注收藏夹数',
`gmt_create` datetime NOT NULL COMMENT '创建时间',
`gmt_modify` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后一次编辑',
PRIMARY KEY (`uid`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户基本信息表';
网页下载后通过XPath进行解析,提取用户各个维度的数据,最后保存到数据库中。
反爬虫策略应对-Headers
一般网站会从几个维度来反爬虫:用户请求的Headers,用户行为,网站和数据加载的方式。从用户请求的Headers反爬虫是最常见的策略,很多网站都会对Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站的防盗链就是检测Referer)。
如果遇到了这类反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。
cookies = {
"d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182",
"login": "NzM5ZDc2M2JkYzYwNDZlOGJlYWQ1YmI4OTg5NDhmMTY=|1480901173|9c296f424b32f241d1471203244eaf30729420f0",
"n_c": "1",
"q_c1": "395b12e529e541cbb400e9718395e346|1479808003000|1468847182000",
"l_cap_id": "NzI0MTQwZGY2NjQyNDQ1NThmYTY0MjJhYmU2NmExMGY=|1480901160|2e7a7faee3b3e8d0afb550e8e7b38d86c15a31bc",
"d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182",
"cap_id": "N2U1NmQwODQ1NjFiNGI2Yzg2YTE2NzJkOTU5N2E0NjI=|1480901160|fd59e2ed79faacc2be1010687d27dd559ec1552a"
}
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.3",
"Referer": "https://www.zhihu.com/"
}
r = requests.get(url, cookies = cookies, headers = headers)
反爬虫策略应对-代理IP池
还有一部分网站是通过检测用户行为,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相同操作。
大多数网站都是前一种情况,对于这种情况,使用IP代理就可以解决。这样的代理ip爬虫经常会用到,最好自己准备一个。有了大量代理ip后可以每请求几次更换一个ip,这在requests或者urllib2中很容易做到,这样就能很容易的绕过第一种反爬虫。目前知乎已经对爬虫做了限制,如果是单个IP的话,一段时间系统便会提示异常流量,无法继续爬取了。因此代理IP池非常关键。网上有个免费的代理IP API: http://api.xicidaili.com/free2016.txt
import requests
import random
class Proxy:
def __init__(self):
self.cache_ip_list = []
# Get random ip from free proxy api url.
def get_random_ip(self):
if not len(self.cache_ip_list):
api_url = 'http://api.xicidaili.com/free2016.txt'
try:
r = requests.get(api_url)
ip_list = r.text.split('\r\n')
self.cache_ip_list = ip_list
except Exception as e:
# Return null list when caught exception.
# In this case, crawler will not use proxy ip.
print e
return {}
proxy_ip = random.choice(self.cache_ip_list)
proxies = {'http': 'http://' + proxy_ip}
return proxies
后续
●使用日志模块记录爬取日志和错误日志
●分布式任务队列和分布式爬虫
爬虫源代码:zhihu-crawler 下载之后通过pip安装相关三方包后,运行$ python crawler.py即可