将 InitSpider 与 splash 一起使用:只解析登录页面?
Using InitSpider with splash: only parsing the login page?
这是 one I asked earlier 的后续问题。
我正在尝试抓取一个必须先登录才能访问的网页。但是认证之后,我需要的网页需要稍微Javascript变成运行才可以查看内容。我所做的是按照说明 here 安装 splash 以尝试渲染 Javascript。然而...
在我切换到 splash 之前,使用 Scrapy 的 InitSpider
进行身份验证没问题。我正在通过登录页面并抓取目标页面 OK(显然 Javascript 工作除外)。但是一旦我添加了通过 splash 传递请求的代码,看起来我并没有解析目标页面。
下面的蜘蛛。 splash 版本(此处)和非 splash 版本之间的唯一区别是函数 def start_requests()
。两者之间的其他一切都是一样的。
import scrapy
from scrapy.spiders.init import InitSpider
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor
class BboSpider(InitSpider):
name = "bbo"
allowed_domains = ["bridgebase.com"]
start_urls = [
"http://www.bridgebase.com/myhands/index.php"
]
login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F"
# authentication
def init_request(self):
return scrapy.http.Request(url=self.login_page, callback=self.login)
def login(self, response):
return scrapy.http.FormRequest.from_response(
response,
formdata={'username': 'USERNAME', 'password': 'PASSWORD'},
callback=self.check_login_response)
def check_login_response(self, response):
if "recent tournaments" in response.body:
self.log("Login successful")
return self.initialized()
else:
self.log("Login failed")
print(response.body)
# pipe the requests through splash so the JS renders
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, self.parse, meta={
'splash': {
'endpoint': 'render.html',
'args': {'wait': 0.5}
}
})
# what to do when a link is encountered
rules = (
Rule(LinkExtractor(), callback='parse_item'),
)
# do nothing on new link for now
def parse_item(self, response):
pass
def parse(self, response):
filename = 'test.html'
with open(filename, 'wb') as f:
f.write(response.body)
现在发生的事情是 test.html
,parse()
的结果,现在只是登录页面本身,而不是我应该在登录后重定向到的页面。
这在日志中说明——通常,我会看到来自 check_login_response()
的 "Login successful" 行,但正如您在下面看到的那样,我似乎甚至没有到达那一步.这是因为 scrapy 现在也通过 splash 发出身份验证请求,并且它被挂在那里了吗?如果是这样,有什么办法可以绕过仅用于身份验证部分的启动?
2016-01-24 14:54:56 [scrapy] INFO: Spider opened
2016-01-24 14:54:56 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-01-24 14:54:56 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-01-24 14:55:02 [scrapy] DEBUG: Crawled (200) <POST http://localhost:8050/render.html> (referer: None)
2016-01-24 14:55:02 [scrapy] INFO: Closing spider (finished)
我很确定我没有正确使用 splash。谁能给我指点一些文档,让我弄清楚发生了什么?
更新
所以,似乎 start_requests
在登录前触发。
这是来自 InitSpider 的代码,减去了注释。
class InitSpider(Spider):
def start_requests(self):
self._postinit_reqs = super(InitSpider, self).start_requests()
return iterate_spider_output(self.init_request())
def initialized(self, response=None):
return self.__dict__.pop('_postinit_reqs')
def init_request(self):
return self.initialized()
InitSpider 使用 initialized
.
调用主程序 start_requests
您的 start_requests
是基础 class 方法的修改版本。所以也许这样的事情会奏效。
from scrapy.utils.spider import iterate_spider_output
...
def start_requests(self):
self._postinit_reqs = my_start_requests()
return iterate_spider_output(self.init_request())
def my_start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, self.parse, meta={
'splash': {
'endpoint': 'render.html',
'args': {'wait': 0.5}
}
})
你需要return self.initialized()
我认为单靠 Splash 无法很好地处理这种特殊情况。
工作思路如下:
- 使用
selenium
and PhantomJS
headless browser登录网站
- 将浏览器 cookies 从
PhantomJS
传递到 Scrapy
代码:
import scrapy
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BboSpider(scrapy.Spider):
name = "bbo"
allowed_domains = ["bridgebase.com"]
login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F"
def start_requests(self):
driver = webdriver.PhantomJS()
driver.get(self.login_page)
driver.find_element_by_id("username").send_keys("user")
driver.find_element_by_id("password").send_keys("password")
driver.find_element_by_name("submit").click()
driver.save_screenshot("test.png")
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.LINK_TEXT, "Click here for results of recent tournaments")))
cookies = driver.get_cookies()
driver.close()
yield scrapy.Request("http://www.bridgebase.com/myhands/index.php", cookies=cookies)
def parse(self, response):
if "recent tournaments" in response.body:
self.log("Login successful")
else:
self.log("Login failed")
print(response.body)
打印 Login successful
和 "hands" 页的 HTML。
完全不需要js就可以获取所有数据,没有javascript的浏览器有link可用,url是同一栏 ?offset=0
。您只需要解析您感兴趣的锦标赛 url 中的查询并创建一个 Formrequest。
import scrapy
from scrapy.spiders.init import InitSpider
from urlparse import parse_qs, urlparse
class BboSpider(InitSpider):
name = "bbo"
allowed_domains = ["bridgebase.com"]
start_urls = [
"http://www.bridgebase.com/myhands/index.php"
]
login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F"
def start_requests(self):
return [scrapy.FormRequest(self.login_page,
formdata={'username': 'foo', 'password': 'bar'}, callback=self.parse)]
def parse(self, response):
yield scrapy.Request("http://www.bridgebase.com/myhands/index.php?offset=0", callback=self.get_all_tournaments)
def get_all_tournaments(self, r):
url = r.xpath("//a/@href[contains(., 'tourneyhistory')]").extract_first()
yield scrapy.Request(url, callback=self.chosen_tourney)
def chosen_tourney(self, r):
url = r.xpath("//a[contains(./text(),'Speedball')]/@href").extract_first()
query = urlparse(url).query
yield scrapy.FormRequest("http://webutil.bridgebase.com/v2/tarchive.php?offset=0", callback=self.get_tourney_data_links,
formdata={k: v[0] for k, v in parse_qs(query).items()})
def get_tourney_data_links(self, r):
print r.xpath("//a/@href").extract()
输出中有很多link,对于你得到tview.php?-t=....
的牌,你可以请求每一个加入http://webutil.bridgebase.com/v2/
,它会给你一个table 在所有易于解析的数据中,还有 link 到 tourney=4796-1455303720-&username=...
与 table 中的每只手关联,tview 的输出片段 link:
class="bbo_tr_t">
<table class="bbo_t_l">
<tr><td class="bbo_tll" align="left">Title</td><td class="bbo_tlv">#4796 Ind. ACBL Fri 2pm</td></tr>
<tr><td class="bbo_tll" align="left">Host</td><td class="bbo_tlv">ACBL</td></tr>
<tr><td class="bbo_tll" align="left">Tables</td><td class="bbo_tlv">9</td></tr>
</table>
</div><div class='sectionbreak'>Section 1 </div><div class='onesection'> <table class='sectiontable' ><tr><th>Name</th><th>Score (IMPs)</th><th class='rank'>Rank</th><th>Prize</th><th>Points</th></tr>
<tr class='odd'><td>colt22</td><td><a href="http://www.bridgebase.com/myhands/hands.php?tourney=4796-1455303720-&username=colt22" target="_blank">42.88</a></td><td class='rank' >1</td><td></td><td>0.90</td></tr>
<tr class='even'><td>francha</td><td><a href="http://www.bridgebase.com/myhands/hands.php?tourney=4796-1455303720-&username=francha" target="_blank">35.52</a></td><td class='rank' >2</td><td></td><td>0.63</td></tr>
<tr class='odd'><td>MSMK</td><td><a href="http://www.bridgebase.com/myhands/hands.php?tourney=4796-1455303720-&username=MSMK" target="_blank">34.38</a></td><td class='rank' >3</td><td></td><td>0.45</td></tr>
剩下的解析我会留给你自己。
这是 one I asked earlier 的后续问题。
我正在尝试抓取一个必须先登录才能访问的网页。但是认证之后,我需要的网页需要稍微Javascript变成运行才可以查看内容。我所做的是按照说明 here 安装 splash 以尝试渲染 Javascript。然而...
在我切换到 splash 之前,使用 Scrapy 的 InitSpider
进行身份验证没问题。我正在通过登录页面并抓取目标页面 OK(显然 Javascript 工作除外)。但是一旦我添加了通过 splash 传递请求的代码,看起来我并没有解析目标页面。
下面的蜘蛛。 splash 版本(此处)和非 splash 版本之间的唯一区别是函数 def start_requests()
。两者之间的其他一切都是一样的。
import scrapy
from scrapy.spiders.init import InitSpider
from scrapy.spiders import Rule
from scrapy.linkextractors import LinkExtractor
class BboSpider(InitSpider):
name = "bbo"
allowed_domains = ["bridgebase.com"]
start_urls = [
"http://www.bridgebase.com/myhands/index.php"
]
login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F"
# authentication
def init_request(self):
return scrapy.http.Request(url=self.login_page, callback=self.login)
def login(self, response):
return scrapy.http.FormRequest.from_response(
response,
formdata={'username': 'USERNAME', 'password': 'PASSWORD'},
callback=self.check_login_response)
def check_login_response(self, response):
if "recent tournaments" in response.body:
self.log("Login successful")
return self.initialized()
else:
self.log("Login failed")
print(response.body)
# pipe the requests through splash so the JS renders
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, self.parse, meta={
'splash': {
'endpoint': 'render.html',
'args': {'wait': 0.5}
}
})
# what to do when a link is encountered
rules = (
Rule(LinkExtractor(), callback='parse_item'),
)
# do nothing on new link for now
def parse_item(self, response):
pass
def parse(self, response):
filename = 'test.html'
with open(filename, 'wb') as f:
f.write(response.body)
现在发生的事情是 test.html
,parse()
的结果,现在只是登录页面本身,而不是我应该在登录后重定向到的页面。
这在日志中说明——通常,我会看到来自 check_login_response()
的 "Login successful" 行,但正如您在下面看到的那样,我似乎甚至没有到达那一步.这是因为 scrapy 现在也通过 splash 发出身份验证请求,并且它被挂在那里了吗?如果是这样,有什么办法可以绕过仅用于身份验证部分的启动?
2016-01-24 14:54:56 [scrapy] INFO: Spider opened
2016-01-24 14:54:56 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-01-24 14:54:56 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-01-24 14:55:02 [scrapy] DEBUG: Crawled (200) <POST http://localhost:8050/render.html> (referer: None)
2016-01-24 14:55:02 [scrapy] INFO: Closing spider (finished)
我很确定我没有正确使用 splash。谁能给我指点一些文档,让我弄清楚发生了什么?
更新
所以,似乎 start_requests
在登录前触发。
这是来自 InitSpider 的代码,减去了注释。
class InitSpider(Spider):
def start_requests(self):
self._postinit_reqs = super(InitSpider, self).start_requests()
return iterate_spider_output(self.init_request())
def initialized(self, response=None):
return self.__dict__.pop('_postinit_reqs')
def init_request(self):
return self.initialized()
InitSpider 使用 initialized
.
start_requests
您的 start_requests
是基础 class 方法的修改版本。所以也许这样的事情会奏效。
from scrapy.utils.spider import iterate_spider_output
...
def start_requests(self):
self._postinit_reqs = my_start_requests()
return iterate_spider_output(self.init_request())
def my_start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, self.parse, meta={
'splash': {
'endpoint': 'render.html',
'args': {'wait': 0.5}
}
})
你需要return self.initialized()
我认为单靠 Splash 无法很好地处理这种特殊情况。
工作思路如下:
- 使用
selenium
andPhantomJS
headless browser登录网站 - 将浏览器 cookies 从
PhantomJS
传递到Scrapy
代码:
import scrapy
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BboSpider(scrapy.Spider):
name = "bbo"
allowed_domains = ["bridgebase.com"]
login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F"
def start_requests(self):
driver = webdriver.PhantomJS()
driver.get(self.login_page)
driver.find_element_by_id("username").send_keys("user")
driver.find_element_by_id("password").send_keys("password")
driver.find_element_by_name("submit").click()
driver.save_screenshot("test.png")
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.LINK_TEXT, "Click here for results of recent tournaments")))
cookies = driver.get_cookies()
driver.close()
yield scrapy.Request("http://www.bridgebase.com/myhands/index.php", cookies=cookies)
def parse(self, response):
if "recent tournaments" in response.body:
self.log("Login successful")
else:
self.log("Login failed")
print(response.body)
打印 Login successful
和 "hands" 页的 HTML。
完全不需要js就可以获取所有数据,没有javascript的浏览器有link可用,url是同一栏 ?offset=0
。您只需要解析您感兴趣的锦标赛 url 中的查询并创建一个 Formrequest。
import scrapy
from scrapy.spiders.init import InitSpider
from urlparse import parse_qs, urlparse
class BboSpider(InitSpider):
name = "bbo"
allowed_domains = ["bridgebase.com"]
start_urls = [
"http://www.bridgebase.com/myhands/index.php"
]
login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F"
def start_requests(self):
return [scrapy.FormRequest(self.login_page,
formdata={'username': 'foo', 'password': 'bar'}, callback=self.parse)]
def parse(self, response):
yield scrapy.Request("http://www.bridgebase.com/myhands/index.php?offset=0", callback=self.get_all_tournaments)
def get_all_tournaments(self, r):
url = r.xpath("//a/@href[contains(., 'tourneyhistory')]").extract_first()
yield scrapy.Request(url, callback=self.chosen_tourney)
def chosen_tourney(self, r):
url = r.xpath("//a[contains(./text(),'Speedball')]/@href").extract_first()
query = urlparse(url).query
yield scrapy.FormRequest("http://webutil.bridgebase.com/v2/tarchive.php?offset=0", callback=self.get_tourney_data_links,
formdata={k: v[0] for k, v in parse_qs(query).items()})
def get_tourney_data_links(self, r):
print r.xpath("//a/@href").extract()
输出中有很多link,对于你得到tview.php?-t=....
的牌,你可以请求每一个加入http://webutil.bridgebase.com/v2/
,它会给你一个table 在所有易于解析的数据中,还有 link 到 tourney=4796-1455303720-&username=...
与 table 中的每只手关联,tview 的输出片段 link:
class="bbo_tr_t">
<table class="bbo_t_l">
<tr><td class="bbo_tll" align="left">Title</td><td class="bbo_tlv">#4796 Ind. ACBL Fri 2pm</td></tr>
<tr><td class="bbo_tll" align="left">Host</td><td class="bbo_tlv">ACBL</td></tr>
<tr><td class="bbo_tll" align="left">Tables</td><td class="bbo_tlv">9</td></tr>
</table>
</div><div class='sectionbreak'>Section 1 </div><div class='onesection'> <table class='sectiontable' ><tr><th>Name</th><th>Score (IMPs)</th><th class='rank'>Rank</th><th>Prize</th><th>Points</th></tr>
<tr class='odd'><td>colt22</td><td><a href="http://www.bridgebase.com/myhands/hands.php?tourney=4796-1455303720-&username=colt22" target="_blank">42.88</a></td><td class='rank' >1</td><td></td><td>0.90</td></tr>
<tr class='even'><td>francha</td><td><a href="http://www.bridgebase.com/myhands/hands.php?tourney=4796-1455303720-&username=francha" target="_blank">35.52</a></td><td class='rank' >2</td><td></td><td>0.63</td></tr>
<tr class='odd'><td>MSMK</td><td><a href="http://www.bridgebase.com/myhands/hands.php?tourney=4796-1455303720-&username=MSMK" target="_blank">34.38</a></td><td class='rank' >3</td><td></td><td>0.45</td></tr>
剩下的解析我会留给你自己。