asyncdefmain(): global session session = aiohttp.ClientSession() scrape_index_tasks = [asyncio.ensure_future(scrape_api()) for _ in range(10000)] await asyncio.gather(*scrape_index_tasks)
if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main())
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait
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
Coroutine: <coroutine object execute at 0x10e0f7830> After calling execute Task: <Task pending coro=<execute() running at demo.py:4>> Number: 1 Task: <Task finished coro=<execute() done, defined at demo.py:4> result=1> After calling loop
Coroutine: <coroutine object execute at 0x10aa33830> After calling execute Task: <Task pending coro=<execute() running at demo.py:4>> Number: 1 Task: <Task finished coro=<execute() done, defined at demo.py:4> result=1> After calling loop
可以发现,其运行效果都是一样的。
6. 绑定回调
另外,我们也可以为某个 task 绑定一个回调方法。比如,我们来看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import asyncio import requests
asyncdefrequest(): url = 'https://www.baidu.com' status = requests.get(url) return status
Waiting for https://httpbin.org/delay/5 Get response from https://httpbin.org/delay/5 response <Response [200]> Waiting for https://httpbin.org/delay/5 ... Get response from https://httpbin.org/delay/5 response <Response [200]> Waiting for https://httpbin.org/delay/5 Get response from https://httpbin.org/delay/5 response <Response [200]> Waiting for https://httpbin.org/delay/5 Get response from https://httpbin.org/delay/5 response <Response [200]> Cost time: 66.64284420013428
Waiting for https://httpbin.org/delay/5 Waiting for https://httpbin.org/delay/5 Waiting for https://httpbin.org/delay/5 Waiting for https://httpbin.org/delay/5 ... Task exception was never retrieved future: <Task finished coro=<request() done, defined at demo.py:8> exception=TypeError("object Response can't be used in 'await' expression")> Traceback (most recent call last): File "demo.py", line 11, in request response = await requests.get(url) TypeError: object Response can't be used in 'await' expression
Waiting for https://httpbin.org/delay/5 Get response fromhttps://httpbin.org/delay/5 response <Response [200]> Waiting for https://httpbin.org/delay/5 Get response from https://httpbin.org/delay/5 response <Response [200]> Waiting for https://httpbin.org/delay/5 ... Get response from https://httpbin.org/delay/5 response <Response [200]> Waiting for https://httpbin.org/delay/5 Get response from https://httpbin.org/delay/5 response <Response [200]> Waiting for https://httpbin.org/delay/5 Get response from https://httpbin.org/delay/5 response <Response [200]> Cost time: 65.394437756259273
with sync_playwright() as p: for browser_type in [p.chromium, p.firefox, p.webkit]: browser = browser_type.launch(headless=False) page = browser.new_page() page.goto('https://www.baidu.com') page.screenshot(path=f'screenshot-{browser_type.name}.png') print(page.title()) browser.close()
Options: -o, --output <file name> saves the generated script to a file --target <language> language to use, one of javascript, python, python-async, csharp (default: "python") -b, --browser <browserType> browser to use, one of cr, chromium, ff, firefox, wk, webkit (default: "chromium") --channel <channel> Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc --color-scheme <scheme> emulate preferred color scheme, "light" or "dark" --device <deviceName> emulate device, for example "iPhone 11" --geolocation <coordinates> specify geolocation coordinates, for example "37.819722,-122.478611" --load-storage <filename> load context storage state from the file, previously saved with --save-storage --lang <language> specify language / locale, for example "en-GB" --proxy-server <proxy> specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080" --save-storage <filename> save context storage state at the end, for later use with --load-storage --timezone <time zone> time zone to emulate, for example "Europe/Rome" --timeout <timeout> timeout for Playwright actions in milliseconds (default: "10000") --user-agent <ua string> specify user agent string --viewport-size <size> specify browser viewport size in pixels, for example "1280, 720" -h, --help display help for command
with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto('https://spa6.scrape.center/') page.wait_for_load_state('networkidle') elements = page.query_selector_all('a.name') for element in elements: print(element.get_attribute('href')) print(element.text_content()) browser.close()
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx 霸王别姬 - Farewell My Concubine /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy 这个杀手不太冷 - Léon /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIz 肖申克的救赎 - The Shawshank Redemption /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI0 泰坦尼克号 - Titanic /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI1 罗马假日 - Roman Holiday /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2 唐伯虎点秋香 - Flirting Scholar /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3 乱世佳人 - Gone with the Wind /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI4 喜剧之王 - The King of Comedy /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5 楚门的世界 - The Truman Show /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA== 狮子王 - The Lion King
url = 'https://spa1.scrape.center/' html = requests.get(url).text print(html)
运行结果如下:
1
<!DOCTYPE html><htmllang=en><head><metacharset=utf-8><metahttp-equiv=X-UA-Compatiblecontent="IE=edge"><metaname=viewportcontent="width=device-width,initial-scale=1"><linkrel=iconhref=/favicon.ico><title>Scrape | Movie</title><linkhref=/css/chunk-700f70e1.1126d090.cssrel=prefetch><linkhref=/css/chunk-d1db5eda.0ff76b36.cssrel=prefetch><linkhref=/js/chunk-700f70e1.0548e2b4.jsrel=prefetch><linkhref=/js/chunk-d1db5eda.b564504d.jsrel=prefetch><linkhref=/css/app.ea9d802a.cssrel=preloadas=style><linkhref=/js/app.1435ecd5.jsrel=preloadas=script><linkhref=/js/chunk-vendors.77daf991.jsrel=preloadas=script><linkhref=/css/app.ea9d802a.cssrel=stylesheet></head><body><noscript><strong>We're sorry but portal doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><divid=app></div><scriptsrc=/js/chunk-vendors.77daf991.js></script><scriptsrc=/js/app.1435ecd5.js></script></body></html>
可以看到,爬取结果就只有这么一点 HTML 内容,而我们在浏览器中打开这个页面,却能看到如图所示的结果:
在 HTML 中,我们只能看到在源码中引用了一些 JavaScript 和 CSS 文件,并没有观察到有任何电影数据信息。
如果遇到这样的情况,这说明我们现在看到的整个页面便是 JavaScript 渲染得到的,浏览器执行了 HTML 中所引用的 JavaScript 文件,JavaScript 通过调用一些数据加载和页面渲染方法,才最终呈现了图中所示的结果。
defmain(): for page in range(1, TOTAL_PAGE + 1): index_data = scrape_index(page) for item in index_data.get('results'): id = item.get('id') detail_data = scrape_detail(id) logging.info('detail data %s', detail_data)
if __name__ == '__main__': main()
这里我们定义了一个 main 方法,首先遍历获取了页码 page,然后把 page 当参数传递给了 scrape_index 方法,得到列表页的数据。接着我们遍历每个列表页的每个结果,获取到每部电影的 id,然后把 id 当作参数传递给 scrape_detail 方法来爬取每部电影的详情数据,并将其赋值为 detail_data,输出即可。
defsave_data(data): collection.update_one({ 'name': data.get('name') }, { '$set': data }, upsert=True)
在这里我们声明了一个 save_data 方法,它接收一个 data 参数,也就是我们刚才提取的电影详情信息。在方法里面,我们调用了 update_one 方法,第一个参数是查询条件,即根据 name 进行查询;第二个参数就是 data 对象本身,就是所有的数据,这里我们用 $set 操作符表示更新操作;第三个参数很关键,这里实际上是 upsert 参数,如果把这个设置为 True,则可以做到存在即更新,不存在即插入的功能,更新会根据第一个参数设置的 name 字段,所以这样可以防止数据库中出现同名的电影数据。
defmain(): for page in range(1, TOTAL_PAGE + 1): index_data = scrape_index(page) for item in index_data.get('results'): id = item.get('id') detail_data = scrape_detail(id) logging.info('detail data %s', detail_data) save_data(detail_data) logging.info('data saved successfully')
JavaScript 有改变网页内容的能力,解析完响应内容之后,就可以调用 JavaScript 来针对解析完的内容对网页进行下一步处理了。比如,通过 document.getElementById().innerHTML 这样的操作,便可以对某个元素内的源代码进行更改,这样网页显示的内容就改变了,这样的操作也被称作 DOM 操作,即对网页文档进行操作,如更改、删除等。
上例中,document.getElementById("myDiv").innerHTML=xmlhttp.responseText 便将 ID 为 myDiv 的节点内部的 HTML 代码更改为服务器返回的内容,这样 myDiv 元素内部便会呈现出服务器返回的新数据,网页的部分内容看上去就更新了。
这样一来,我们就可以用索引来获取对应的内容了。例如,如果想取第一个元素里的 name 属性,就可以使用如下方式:
1 2
data[0]['name'] data[0].get('name')
得到的结果都是:
1
Bob
通过中括号加 0 索引,可以得到第一个字典元素,然后再调用其键名即可得到相应的键值。获取键值时有两种方式,一种是中括号加键名,另一种是通过 get 方法传入键名。这里推荐使用 get 方法,这样如果键名不存在,则不会报错,会返回 None。另外,get 方法还可以传入第二个参数(即默认值),示例如下:
可以看到两个结果都是 SelectorList 对象,它其实是一个可迭代对象。另外可以用 len 方法获取它的长度,都是 3,提取结果代表的节点其实也是一样的,都是第 1、3、5 个 li 节点,每个节点还是以 Selector 对象的形式返回了,其中每个 Selector 对象的 data 属性里面包含了提取节点的 HTML 代码。
好,既然刚才提取的结果是一个可迭代对象 SelectorList,那么要获取提取到的所有 li 节点的文本内容就要对结果进行遍历了,写法如下:
1 2 3 4 5 6
from parsel import Selector selector = Selector(text=html) items = selector.css('.item-0') for item in items: text = item.xpath('.//text()').get() print(text)
这里 get 方法的作用是从 SelectorList 里面提取第一个 Selector 对象,然后输出其中的结果。
我们再看一个实例:
1 2
result = selector.xpath('//li[contains(@class, "item-0")]//text()').get() print(result)
输出结果如下:
1
firstitem
其实这里我们使用 //li[contains(@class, "item-0")]//text() 选取了所有 class 包含 item-0 的 li 节点的文本内容。应该来说,返回结果 SelectorList 应该对应三个 li 对象,而这里 get 方法仅仅返回了第一个 li 对象的文本内容,因为其实它会只提取第一个 Selector 对象的结果。
那有没有能提取所有 Selector 的对应内容的方法呢?有,那就是 getall 方法。
所以如果要提取所有对应的 li 节点的文本内容的话,写法可以改写为如下内容:
1 2
result = selector.xpath('//li[contains(@class, "item-0")]//text()').getall() print(result)
from parsel import Selector selector = Selector(text=html) result = selector.css('.item-0.active a::attr(href)').get() print(result) result = selector.xpath('//li[contains(@class, "item-0") and contains(@class, "active")]/a/@href').get() print(result)
这里我们实现了两种写法,分别用 css 和 xpath 方法实现。我们根据同时包含 item-0 和 active 这两个 class 为依据来选取第三个 li 节点,然后进一步选取了里面的 a 节点,对于 CSS Selector,选取属性需要加 ::attr() 并传入对应的属性名称来选取,对于 XPath,直接用 /@ 再加属性名称即可选取。最后统一用 get 方法提取结果即可。
我们一般会用 // 开头的 XPath 规则来选取所有符合要求的节点。这里以前面的 HTML 文本为例,如果要选取所有节点,可以这样实现:
1 2 3 4
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//*') print(result)
运行结果如下:
1
[<Element html at 0x10510d9c8>, <Element body at 0x10510da08>, <Element div at 0x10510da48>, <Element ul at 0x10510da88>, <Element li at 0x10510dac8>, <Element a at 0x10510db48>, <Element li at 0x10510db88>, <Element a at 0x10510dbc8>, <Element li at 0x10510dc08>, <Element a at 0x10510db08>, <Element li at 0x10510dc48>, <Element a at 0x10510dc88>, <Element li at 0x10510dcc8>, <Element a at 0x10510dd08>]
这里使用 * 代表匹配所有节点,也就是整个 HTML 文本中的所有节点都会被获取。可以看到,返回形式是一个列表,每个元素是 Element 类型,其后跟了节点的名称,如 html、body、div、ul、li、a 等,所有节点都包含在列表中了。
当然,此处匹配也可以指定节点名称。如果想获取所有 li 节点,示例如下:
1 2 3 4 5
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li') print(result) print(result[0])
这里要选取所有 li 节点,可以使用 //,然后直接加上节点名称即可,调用时直接使用 xpath 方法即可。
运行结果如下:
1 2
[<Element li at 0x105849208>, <Element li at 0x105849248>, <Element li at 0x105849288>, <Element li at 0x1058492c8>, <Element li at 0x105849308>] <Element li at 0x105849208>
这里可以看到,提取结果是一个列表形式,其中每个元素都是一个 Element 对象。如果要取出其中一个对象,可以直接用中括号加索引,如 [0]。
6. 子节点
我们通过 / 或 // 即可查找元素的子节点或子孙节点。假如现在想选择 li 节点的所有直接子节点 a,可以这样实现:
1 2 3 4 5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li/a') print(result)
这里通过追加 /a 即选择了所有 li 节点的所有直接子节点 a。因为 //li 用于选中所有 li 节点,/a 用于选中 li 节点的所有直接子节点 a,二者组合在一起即获取所有 li 节点的所有直接子节点 a。
运行结果如下:
1
[<Element a at 0x106ee8688>, <Element a at 0x106ee86c8>, <Element a at 0x106ee8708>, <Element a at 0x106ee8748>, <Element a at 0x106ee8788>]
此处的 / 用于选取直接子节点,如果要获取所有子孙节点,就可以使用 //。例如,要获取 ul 节点下的所有子孙节点 a,可以这样实现:
1 2 3 4 5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//ul//a') print(result)
运行结果是相同的。
但是如果这里用 //ul/a,就无法获取任何结果了。因为 / 用于获取直接子节点,而在 ul 节点下没有直接的 a 子节点,只有 li 节点,所以无法获取任何匹配结果,代码如下:
1 2 3 4 5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//ul/a') print(result)
比如,现在首先选中 href 属性为 link4.html 的 a 节点,然后获取其父节点,再获取其 class 属性,相关代码如下:
1 2 3 4 5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//a[@href="link4.html"]/../@class') print(result)
运行结果如下:
1
['item-1']
检查一下结果发现,这正是我们获取的目标 li 节点的 class 属性。
同时,我们也可以通过 parent:: 来获取父节点,代码如下:
1 2 3 4
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//a[@href="link4.html"]/parent::*/@class') print(result)
8. 属性匹配
在选取的时候,我们还可以用 @ 符号进行属性过滤。比如,这里如果要选取 class 为 item-0 的 li 节点,可以这样实现:
1 2 3 4
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]') print(result)
这里我们通过加入 [@class="item-0"],限制了节点的 class 属性为 item-0,而 HTML 文本中符合条件的 li 节点有两个,所以结果应该返回两个匹配到的元素。结果如下:
1
<Element li at 0x10a399288>, <Element li at 0x10a3992c8>
可见,匹配结果正是两个,至于是不是那正确的两个,后面再验证。
9. 文本获取
我们用 XPath 中的 text 方法获取节点中的文本,接下来尝试获取前面 li 节点中的文本,相关代码如下:
1 2 3 4 5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]/text()') print(result)
运行结果如下:
1
['\n ']
奇怪的是,我们并没有获取到任何文本,只获取到了一个换行符,这是为什么呢?因为 XPath 中 text 方法前面是 /,而此处 / 的含义是选取直接子节点,很明显 li 的直接子节点都是 a 节点,文本都是在 a 节点内部的,所以这里匹配到的结果就是被修正的 li 节点内部的换行符,因为自动修正的 li 节点的尾标签换行了。
其中一个节点因为自动修正,li 节点的尾标签添加的时候换行了,所以提取文本得到的唯一结果就是 li 节点的尾标签和 a 节点的尾标签之间的换行符。
因此,如果想获取 li 节点内部的文本,就有两种方式,一种是先选取 a 节点再获取文本,另一种就是使用 //。接下来,我们来看下二者的区别。
首先,选取 a 节点再获取文本,代码如下:
1 2 3 4
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]/a/text()') print(result)
运行结果如下:
1
['first item', 'fifth item']
可以看到,这里的返回值是两个,内容都是属性为 item-0 的 li 节点的文本,这也印证了前面属性匹配的结果是正确的。
这里我们是逐层选取的,先选取了 li 节点,又利用 / 选取了其直接子节点 a,然后再选取其文本,得到的结果恰好是符合我们预期的两个结果。
再来看下用另一种方式(即使用 //)选取的结果,代码如下:
1 2 3 4 5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]//text()') print(result)
运行结果如下:
1
['first item', 'fifth item', '\n ']
不出所料,这里的返回结果是 3 个。可想而知,这里是选取所有子孙节点的文本,其中前两个就是 li 的子节点 a 内部的文本,另外一个就是最后一个 li 节点内部的文本,即换行符。
所以说,如果要想获取子孙节点内部的所有文本,可以直接用 // 加 text 方法的方式,这样可以保证获取到最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。如果想获取某些特定子孙节点下的所有文本,可以先选取到特定的子孙节点,然后再调用 text 方法获取其内部文本,这样可以保证获取的结果是整洁的。
10. 属性获取
我们知道用 text 方法可以获取节点内部文本,那么节点属性该怎样获取呢?其实还是用 @ 符号就可以。例如,我们想获取所有 li 节点下所有 a 节点的 href 属性,代码如下:
1 2 3 4 5
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li/a/@href') print(result)
from lxml import etree text = ''' <li class="li li-first"><a href="link.html">first item</a></li> ''' html = etree.HTML(text) result = html.xpath('//li[@class="li"]/a/text()') print(result)
这里 HTML 文本中 li 节点的 class 属性有两个值 li 和 li-first,此时如果还想用之前的属性匹配获取,就无法匹配了,此时的运行结果如下:
1
[]
这时就需要用 contains 方法了,代码可以改写如下:
1 2 3 4 5 6 7
from lxml import etree text = ''' <li class="li li-first"><a href="link.html">first item</a></li> ''' html = etree.HTML(text) result = html.xpath('//li[contains(@class, "li")]/a/text()') print(result)
另外,我们可能还遇到一种情况,那就是根据多个属性确定一个节点,这时就需要同时匹配多个属性。此时可以使用运算符 and 来连接,示例如下:
1 2 3 4 5 6 7
from lxml import etree text = ''' <li class="li li-first" name="item"><a href="link.html">first item</a></li> ''' html = etree.HTML(text) result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()') print(result)
这里的 li 节点又增加了一个属性 name。要确定这个节点,需要同时根据 class 和 name 属性来选择,一个条件是 class 属性里面包含 li 字符串,另一个条件是 name 属性为 item 字符串,二者需要同时满足,需要用 and 操作符相连,相连之后置于中括号内进行条件筛选。运行结果如下:
1
['first item']
这里的 and 其实是 XPath 中的运算符。另外,还有很多运算符,如 or、mod 等,在此总结为表 3-。
text = ''' <div> <ul> <li class="item-0"><a href="link1.html"><span>first item</span></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) result = html.xpath('//li[1]/ancestor::*') print(result) result = html.xpath('//li[1]/ancestor::div') print(result) result = html.xpath('//li[1]/attribute::*') print(result) result = html.xpath('//li[1]/child::a[@href="link1.html"]') print(result) result = html.xpath('//li[1]/descendant::span') print(result) result = html.xpath('//li[1]/following::*[2]') print(result) result = html.xpath('//li[1]/following-sibling::*') print(result)
运行结果如下:
1 2 3 4 5 6 7
[<Element html at 0x107941808>, <Element body at 0x1079418c8>, <Element div at 0x107941908>, <Element ul at 0x107941948>] [<Element div at 0x107941908>] ['item-0'] [<Element a at 0x1079418c8>] [<Element span at 0x107941948>] [<Element a at 0x1079418c8>] [<Element li at 0x107941948>, <Element li at 0x107941988>, <Element li at 0x1079419c8>, <Element li at 0x107941a08>]
第一次选择时,我们调用了 ancestor 轴,可以获取所有祖先节点。其后需要跟两个冒号,然后是节点的选择器,这里我们直接使用 *,表示匹配所有节点,因此返回结果是第一个 li 节点的所有祖先节点,包括 html、body、div 和 ul。
第二次选择时,我们又加了限定条件,这次在冒号后面加了 div,这样得到的结果就只有 div 这个祖先节点了。
第三次选择时,我们调用了 attribute 轴,可以获取所有属性值,其后跟的选择器还是 *,这代表获取节点的所有属性,返回值就是 li 节点的所有属性值。
第四次选择时,我们调用了 child 轴,可以获取所有直接子节点。这里我们又加了限定条件,选取 href 属性为 link1.html 的 a 节点。
第五次选择时,我们调用了 descendant 轴,可以获取所有子孙节点。这里我们又加了限定条件获取 span 节点,所以返回的结果只包含 span 节点而不包含 a 节点。
第六次选择时,我们调用了 following 轴,可以获取当前节点之后的所有节点。这里我们虽然使用的是 * 匹配,但又加了索引选择,所以只获取了第二个后续节点。
drama:直接提取 class 为 drama 的节点内部的 p 节点的文本即可,同样用 search 方法可以提取。
score:直接提取 class 为 score 的 p 节点的文本即可,但由于提取结果是字符串,所以我们还需要把它转成浮点数,即 float 类型。
最后,上述的字段提取完毕之后,构造一个字典返回即可。
这样,我们就成功完成了详情页的提取和分析了。
最后,main 方法稍微改写一下,增加这两个方法的调用,改写如下:
1 2 3 4 5 6 7 8
defmain(): for page in range(1, TOTAL_PAGE + 1): index_html = scrape_index(page) detail_urls = parse_index(index_html) for detail_url in detail_urls: detail_html = scrape_detail(detail_url) data = parse_detail(detail_html) logging.info('get detail data %s', data)
这里我们首先遍历了 detail_urls,获取了每个详情页的 URL,然后依次调用了 scrape_detail 和 parse_detail 方法,最后得到了每个详情页的提取结果,赋值为 data 并输出。
defmain(): for page in range(1, TOTAL_PAGE + 1): index_html = scrape_index(page) detail_urls = parse_index(index_html) for detail_url in detail_urls: detail_html = scrape_detail(detail_url) data = parse_detail(detail_html) logging.info('get detail data %s', data) logging.info('saving data to json file') save_data(data) logging.info('data saved successfully')
这里就是加了 save_data 方法的调用,并加了一些日志信息。
重新运行,我们看下输出结果:
1 2 3 4 5 6 7 8 9 10 11 12
2020-03-0901:10:27,094 - INFO: scraping https://ssr1.scrape.center/page/1... 2020-03-0901:10:28,019 - INFO: get detail url https://ssr1.scrape.center/detail/1 2020-03-0901:10:28,019 - INFO: scraping https://ssr1.scrape.center/detail/1... 2020-03-0901:10:29,183 - INFO: get detail data {'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'name': '霸王别姬 - Farewell My Concubine', 'categories': ['剧情', '爱情'], 'published_at': '1993-07-26', 'drama': '影片借一出《霸王别姬》的京戏,牵扯出三个人之间一段随时代风云变幻的爱恨情仇。段小楼(张丰毅 饰)与程蝶衣(张国荣 饰)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。段小楼在认为该成家立业之时迎娶了名妓菊仙(巩俐 饰),致使程蝶衣认定菊仙是可耻的第三者,使段小楼做了叛徒,自此,三人围绕一出《霸王别姬》生出的爱恨情仇战开始随着时代风云的变迁不断升级,终酿成悲剧。', 'score': 9.5} 2020-03-0901:10:29,183 - INFO: saving data to json file 2020-03-0901:10:29,288 - INFO: data saved successfully 2020-03-0901:10:29,288 - INFO: get detail url https://ssr1.scrape.center/detail/2 2020-03-0901:10:29,288 - INFO: scraping https://ssr1.scrape.center/detail/2... 2020-03-0901:10:30,250 - INFO: get detail data {'cover': 'https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c', 'name': '这个杀手不太冷 - Léon', 'categories': ['剧情', '动作', '犯罪'], 'published_at': '1994-09-14', 'drama': '里昂(让·雷诺 饰)是名孤独的职业杀手,受人雇佣。一天,邻居家小姑娘马蒂尔德(纳塔丽·波特曼 饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包毒品而遭恶警(加里·奥德曼 饰)杀害全家的惩罚。马蒂尔德 得到里昂的留救,幸免于难,并留在里昂那里。里昂教小女孩使枪,她教里昂法文,两人关系日趋亲密,相处融洽。 女孩想着去报仇,反倒被抓,里昂及时赶到,将女孩救回。混杂着哀怨情仇的正邪之战渐次升级,更大的冲突在所难免……', 'score': 9.5} 2020-03-0901:10:30,250 - INFO: saving data to json file 2020-03-0901:10:30,253 - INFO: data saved successfully ...
使用 get 方法成功实现一个 GET 请求,这倒不算什么,更方便之处在于其他的请求类型依然可以用一句话来完成,示例如下:
1 2 3 4 5 6 7
import requests
r = requests.get('https://httpbin.org/get') r = requests.post('https://httpbin.org/post') r = requests.put('https://httpbin.org/put') r = requests.delete('https://httpbin.org/delete') r = requests.patch('https://httpbin.org/patch')
headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' } r = requests.get('https://ssr1.scrape.center/', headers=headers) print(r.text)
当然,我们可以在 headers 这个参数中任意添加其他的字段信息。
4. POST 请求
前面我们了解了最基本的 GET 请求,另外一种比较常见的请求方式是 POST。使用 requests 实现 POST 请求同样非常简单,示例如下:
1 2 3 4 5
import requests
data = {'name': 'germey', 'age': '25'} r = requests.post("https://httpbin.org/post", data=data) print(r.text)
requests.exceptions.SSLError: HTTPSConnectionPool(host='ssr2.scrape.center', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)')))
还是上面的 HTML 文本,如果想获取所有 a 节点的超链接、歌手和歌名,就可以将 search 方法换成 findall 方法。如果有返回结果的话,就是列表类型,所以需要遍历一下来依次获取每组内容。代码如下:
1 2 3 4 5 6
results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S) print(results) print(type(results)) for result in results: print(result) print(result[0], result[1], result[2])
During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/var/py/python/urllibtest.py", line 4, in <module> response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1) ... urllib.error.URLError: <urlopen error _ssl.c:1059: The handshake operation timed out>
这里首先尝试连接一下数据库。假设当前的 MySQL 运行在本地,用户名为 root,密码为 123456,运行端口为 3306。这里利用 PyMySQL 先连接 MySQL,然后创建一个新的数据库,名字叫作 spiders,代码如下:
1 2 3 4 5 6 7 8 9
import pymysql
db = pymysql.connect(host='localhost',user='root', password='123456', port=3306) cursor = db.cursor() cursor.execute('SELECT VERSION()') data = cursor.fetchone() print('Database version:', data) cursor.execute("CREATE DATABASE spiders DEFAULT CHARACTER SET utf8mb4") db.close()
运行结果如下:
1
Database version: ('8.0.19',)
这里通过 PyMySQL 的 connect 方法声明一个 MySQL 连接对象 db,此时需要传入 MySQL 运行的 host(即 IP)。由于 MySQL 在本地运行,所以传入的是 localhost。如果 MySQL 在远程运行,则传入其公网 IP 地址。后续的参数 user 即用户名,password 即密码,port 即端口(默认为 3306)。
db = pymysql.connect(host='localhost', user='root', password='123456', port=3306, db='spiders') cursor = db.cursor() sql = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY (id))' cursor.execute(sql) db.close()
我们在做爬虫的过程中经常会遇到这样的情况,最初爬虫正常运行,正常抓取数据,一切看起来都是那么美好,然而一杯茶的功夫可能就会出现错误,比如 403 Forbidden,这时打开网页一看,可能会看到 “您的 IP 访问频率太高” 这样的提示。出现这种现象的原因是网站采取了一些反爬虫措施。比如,服务器会检测某个 IP 在单位时间内的请求次数,如果超过了这个阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封 IP。
既然服务器检测的是某个 IP 单位时间的请求次数,那么借助某种方式来伪装我们的 IP,让服务器识别不出是由我们本机发起的请求,不就可以成功防止封 IP 了吗?
一种有效的方式就是使用代理,后面会详细说明代理的用法。在这之前,需要先了解下代理的基本原理,它是怎样实现伪装 IP 的呢?
1. 基本原理
代理实际上指的就是代理服务器,英文叫作 Proxy Server,它的功能是代理网络用户去取得网络信息。形象地说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给 Web 服务器,Web 服务器把响应传回给我们。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥,此时本机不是直接向 Web 服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后由代理服务器再发送给 Web 服务器,接着由代理服务器再把 Web 服务器返回的响应转发给本机。这样我们同样可以正常访问网页,但这个过程中 Web 服务器识别出的真实 IP 就不再是我们本机的 IP 了,就成功实现了 IP 伪装,这就是代理的基本原理。
for i in range(1, TOTAL + 1): url = f'https://ssr1.scrape.center/detail/{i}' request = requests.Request('GET', url) channel.basic_publish(exchange='', routing_key=QUEUE_NAME, properties=pika.BasicProperties( delivery_mode=2, ), body=pickle.dumps(request)) print(f'Put request of {url}')
<!DOCTYPE html> <html> <head> <metacharset="UTF-8" /> <title>This is a Demo</title> </head> <body> <divid="container"> <divclass="wrapper"> <h2class="title">Hello World</h2> <pclass="text">Hello, this is a paragraph.</p> </div> </div> </body> </html>
这是最基本的 HTML 代码,我们将其保存为一个 test.html 文件,然后把它放在某台具有固定公网 IP 的主机上,主机上装上 Apache 或 Nginx 等服务器,这样这台主机就可以作为服务器了,其他人便可以通过访问服务器看到这个页面,这就搭建了一个最简单的网站。
这种网页的内容是 HTML 代码编写的,文字、图片等内容均通过写好的 HTML 代码来指定,这种页面叫作静态网页。它加载速度快,编写简单,但是存在很大的缺陷,如可维护性差,不能根据 URL 灵活多变地显示内容等。例如,我们想要给这个网页的 URL 传入一个 name 参数,让其在网页中显示出来,是无法做到的。
那么,我们怎样利用 Cookies 保持状态呢?当客户端第一次请求服务器时,服务器会返回一个响应头中带有 Set-Cookie 字段的响应给客户端,用来标记是哪一个用户,客户端浏览器会把 Cookies 保存起来。当浏览器下一次再请求该网站时,浏览器会把此 Cookies 放到请求头一起提交给服务器,Cookies 携带了 Session ID 信息,服务器检查该 Cookies 即可找到对应的 Session 是什么,然后再判断 Session 来辨认用户状态。
在成功登录某个网站时,服务器会告诉客户端设置哪些 Cookies 信息。在后续访问页面时,客户端会把 Cookies 发送给服务器,服务器再找到对应的 Session 加以判断。如果 Session 中的某些设置登录状态的变量是有效的,那就证明用户处于登录状态,此时返回登录之后才可以查看的网页内容,浏览器再进行解析便可以看到了。
反之,如果传给服务器的 Cookies 是无效的,或者 Session 已经过期了,我们将不能继续访问页面,此时可能会收到错误的响应或者跳转到登录页面重新登录。
但是当我们关闭浏览器时,浏览器不会主动在关闭之前通知服务器它将要关闭,所以服务器根本不会有机会知道浏览器已经关闭。之所以会有这种错觉,是因为大部分网站都使用会话 Cookie 来保存 Session ID 信息,而关闭浏览器后 Cookies 就消失了,再次连接服务器时,也就无法找到原来的 Session 了。如果服务器设置的 Cookies 保存到硬盘上,或者使用某种手段改写浏览器发出的 HTTP 请求头,把原来的 Cookies 发送给服务器,则再次打开浏览器,仍然能够找到原来的 Session ID,依旧还是可以保持登录状态的。
HTML 是用来描述网页的一种语言,网页包括文字、按钮、图片和视频等各种复杂的元素,其基础架构就是 HTML。不同类型的元素通过不同类型的标签来表示,如图片用 img 标签表示,视频用 video 标签表示,段落用 p 标签表示,它们之间的布局又常通过布局标签 div 嵌套组合而成,各种标签通过不同的排列和嵌套才形成了网页的框架。
那 HTML 长什么样子呢?我们可以随意打开一个网站,比如淘宝 https://www.taobao.com,然后右键菜单点击“检查元素”或者按 F12 快捷键,即可打开浏览器开发者工具,切换到 Elements 面板,这时候就可以看到这里呈现的就是淘宝网对应的 HTML,它包含了一系列标签,浏览器解析这些标签后,便会在网页中渲染成一个个的节点,这便形成了我们平常看到的网页。比如这里可以看到一个输入框就对应一个 input 标签,可以用于输入文字。
我们首先用例子来感受一下 HTML 的基本结构。新建一个文本文件,名称叫做 test.html,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<!DOCTYPE html> <html> <head> <metacharset="UTF-8" /> <title>This is a Demo</title> </head> <body> <divid="container"> <divclass="wrapper"> <h2class="title">Hello World</h2> <pclass="text">Hello, this is a paragraph.</p> </div> </div> </body> </html>
这就是一个最简单的 HTML 实例。开头用 DOCTYPE 定义了文档类型,其次最外层是 html 标签,最后还有对应的结束标签来表示闭合,其内部是 head 标签和 body 标签,分别代表网页头和网页体,它们也需要结束标签。head 标签内定义了一些页面的配置和引用,如:
1
<metacharset="UTF-8" />
它指定了网页的编码为 UTF-8。
title 标签则定义了网页的标题,会显示在网页的选项卡中,不会显示在正文中。body 标签内则是在网页正文中显示的内容。div 标签定义了网页中的区块,它的 id 是 container,这是一个非常常用的属性,且 id 的内容在网页中是唯一的,我们可以通过它来获取这个区块。然后在此区块内又有一个 div 标签,它的 class 为 wrapper,这也是一个非常常用的属性,经常与 CSS 配合使用来设定样式。然后此区块内部又有一个 h2 标签,这代表一个二级标题。另外,还有一个 p 标签,这代表一个段落。在这两者中直接写入相应的内容即可在网页中呈现出来,它们也有各自的 class 属性。
将代码保存后,双击该文件在浏览器中打开,可以看到如图所示的内容。
可以看到,选项卡上显示了 This is a Demo 字样,这是我们在 head 中的 title 里定义的文字。而网页正文是 body 标签内部定义的各个元素生成的,可以看到这里显示了二级标题和段落。
这个实例便是网页的一般结构。一个网页的标准形式是 html 标签内嵌套 head 和 body 标签,head 内定义网页的配置和引用,body 内定义网页的正文。
3 节点树及节点间的关系
在 HTML 中,所有标签定义的内容都是节点,它们构成了一个 HTML 节点树,也称之为 HTML DOM 树。
我们先看下什么是 DOM。DOM 是 W3C(万维网联盟)的标准,其英文全称 Document Object Model,即文档对象模型。它定义了访问 HTML 和 XML 文档的标准。根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点。
整个网站文档是一个文档节点。
每个 html 标签对应一个根元素节点,即上例中的 html 标签,这属于一个跟元素节点。
节点内的文本是文本节点,比如 a 节点代表一个超链接,它内部的文本也被认为是一个文本节点。
每个节点的属性是属性节点,比如 a 节点有一个 href 属性,它就是一个属性节点。
注释是注释节点,在 HTML 中有特殊的语法会被解析为注释,但其也会对应一个节点。
所以,HTML DOM 将 HTML 文档视作树结构,这种结构被称为节点树,如图所示:
通过 HTML DOM,树中的所有节点均可通过 JavaScript 访问,所有 HTML 节点元素均可被修改,也可以被创建或删除。
在 CSS 中,我们使用 CSS 选择器来定位节点。例如,上例中 div 节点的 id 为 container,那么就可以表示为 #container,其中 # 开头代表选择 id,其后紧跟 id 的名称。另外,如果我们想选择 class 为 wrapper 的节点,便可以使用.wrapper,这里以点(.)开头代表选择 class,其后紧跟 class 的名称。另外,还有一种选择方式,那就是根据标签名筛选,例如想选择二级标题,直接用 h2 即可。这是最常用的 3 种表示,分别是根据 id、class、标签名筛选,请牢记它们的写法。
另外,CSS 选择器还支持嵌套选择,各个选择器之间加上空格分隔开便可以代表嵌套关系,如 #container .wrapper p 则代表先选择 id 为 container 的节点,然后选中其内部的 class 为 wrapper 的节点,然后再进一步选中其内部的 p 节点。另外,如果不加空格,则代表并列关系,如 div#container .wrapper p.text 代表先选择 id 为 container 的 div 节点,然后选中其内部的 class 为 wrapper 的节点,再进一步选中其内部的 class 为 text 的 p 节点。这就是 CSS 选择器,其筛选功能还是非常强大的。
Slidev is a slides maker and presenter designed for developers, consist of the following features
- 📝 **Text-based** - focus on the content with Markdown, and then style them later - 🎨 **Themable** - theme can be shared and used with npm packages - 🧑💻 **Developer Friendly** - code highlighting, live coding with autocompletion - 🤹 **Interactive** - embedding Vue components to enhance your expressions - 🎥 **Recording** - built-in recording and camera view - 📤 **Portable** - export into PDF, PNGs, or even a hostable SPA - 🛠 **Hackable** - anything possible on a webpage
<br> <br>
Read more about [Why Slidev?](https://sli.dev/guide/why)
You can use Vue components directly inside your slides.
We have provided a few built-in components like `<Tweet/>` and `<Youtube/>` that you can use directly. And adding your custom components is also super easy.
parser = argparse.ArgumentParser(description='Scrape Function') parser.add_argument('url', type=str, help='an integer for the accumulator') parser.add_argument('timeout', type=int, help='sum the integers (default: find the max)')
if __name__ == '__main__': args = parser.parse_args() scrape(args.url, args.timeout)
这样我们才能顺利地使用命令行来调用这个脚本:
1
python3 main.py https://www.baidu.com 10
是不是感觉非常麻烦?argparse 写起来又臭又长,想想就费劲。
Fire
但接下来我们要介绍一个库,用它我们只需要两行代码就可以做到如上操作。
这个库的名字叫做Fire,它可以快速为某个 Python 方法或者类添加命令行的参数支持。
先看看安装方法,使用 pip3 安装即可:
1
pip3 install fire
这样我们就安装好了。
使用
下面我们来看几个例子。
方法支持
第一个代码示例如下:
1 2 3 4 5 6 7
import fire
defhello(name="World"): return"Hello %s!" % name
if __name__ == '__main__': fire.Fire(hello)
这里我们定义了一个 hello 方法,然后接收一个 name 参数,默认值是 World,接着输出了 Hello 加 name 这个字符串。
然后接着我们导入了 fire 这个库,调用它的 Fire 方法并传入 hello 这个方法声明,会发生什么事情呢?
我们把这段代码保存为 demo1.py,接着用 Python3 来运行一下:
1
python3 demo1.py
运行结果如下:
1
Hello World!
看起来并没有什么不同。
但我们这时候如果运行如下命令,就可以看到一些神奇的事情了:
1
python3 demo1.py --help
运行结果如下:
1 2 3 4 5 6 7 8 9
NAME demo1.py
SYNOPSIS demo1.py <flags>
FLAGS --name=NAME Default: 'World'
可以看到,这里它将 name 这个参数转化成了命令行的一个可选参数,我们可以通过 —-name 来替换 name 参数。
我们来试下:
1
python3 demo1.py --name 123
这里我们传入了一个 name 参数是 123,这时候我们就发现运行结果就变成了如下内容:
1
Hello 123!
是不是非常方便?我们没有借助 argparse 就轻松完成了命令行参数的支持和替换。
那如果我们将 name 这个参数的默认值取消呢?代码改写如下:
1 2 3 4 5 6 7
import fire
defhello(name): return"Hello %s!" % name
if __name__ == '__main__': fire.Fire(hello)
这时候重新运行:
1
python3 demo1.py --help
就可以看到结果变成了如下内容:
1 2 3 4 5 6 7 8 9 10 11
NAME demo1.py
SYNOPSIS demo1.py NAME
POSITIONAL ARGUMENTS NAME
NOTES You can also use flags syntax for POSITIONAL ARGUMENTS
这时候我们发现 name 这个参数就变成了必传参数,我们必须在命令行里指定这个参数内容,调用就会变成如下命令:
1
python3 demo1.py 123
运行结果还是一样的。
类支持
当然 fire 这个库不仅仅支持给方法添加命令行的支持,还支持给一个类添加命令行的支持。
下面我们再看一个例子:
1 2 3 4 5 6 7 8
import fire
classCalculator(object): defdouble(self, number): return2 * number
OPTIONS: -p, --port Port to listen (default: 7681, use `0` for random port) -i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock) -c, --credential Credential for Basic Authentication (format: username:password) -u, --uid User id to run with -g, --gid Group id to run with -s, --signal Signal to send to the command when exit it (default: 1, SIGHUP) -a, --url-arg Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar) -R, --readonlyDo not allow clients to write to the TTY -t, --client-option Send option to client (format: key=value), repeat to add more options -T, --terminal-type Terminal type to report, default: xterm-256color -O, --check-originDo not allow websocket connection from different origin -m, --max-clients Maximum clients to support (default: 0, no limit) -o, --once Accept only one client and exit on disconnection -B, --browser Open terminal with the default system browser -I, --index Custom index.html path -b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here) -P, --ping-interval Websocket ping interval(sec) (default: 300) -6, --ipv6 Enable IPv6 support -S, --ssl Enable SSL -C, --ssl-cert SSL certificate file path -K, --ssl-key SSL key file path -A, --ssl-ca SSL CA file path for client certificate verification -d, --debug Set log level (default: 7) -v, --version Print the version and exit -h, --help Print this text and exit
Visit https://github.com/tsl0922/ttyd to get more information and report bugs.
defrun(self): global l ret = subprocess.Popen( self.command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) for i in iter(ret.stdout.readline, b""): res = i.decode().strip() print(res) l.append(res)
classServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): defdo_GET(self): global l if self.path == uri: self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write(l)
if __name__ == '__main__': # New Thread: Get Command Result t1 = thread('1', sys.argv[1]) t1.start() # Webserver port = int(sys.argv[2]) print("URL: http://HOST:{0}{1}".format(port, uri)) Handler = ServerHandler httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', port), Handler) httpd.serve_forever()
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 from selenium.common.exceptions import WebDriverException import time from loguru import logger
COUNT = 1000
for i in range(1, COUNT + 1): try: browser = webdriver.Chrome() wait = WebDriverWait(browser, 10) browser.get('https://captcha1.scrape.center/') button = wait.until(EC.element_to_be_clickable( (By.CSS_SELECTOR, '.el-button'))) button.click() captcha = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, '.geetest_slicebg.geetest_absolute'))) time.sleep(5) captcha.screenshot(f'data/captcha/images/captcha_{i}.png') except WebDriverException as e: logger.error(f'webdriver error occurred {e.msg}') finally: browser.close()
with sync_playwright() as p: for browser_type in [p.chromium, p.firefox, p.webkit]: browser = browser_type.launch(headless=False) page = browser.new_page() page.goto('https://www.baidu.com') page.screenshot(path=f'screenshot-{browser_type.name}.png') print(page.title()) browser.close()
Options: -o, --output <file name> saves the generated script to a file --target <language> language to use, one of javascript, python, python-async, csharp (default: "python") -b, --browser <browserType> browser to use, one of cr, chromium, ff, firefox, wk, webkit (default: "chromium") --channel <channel> Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc --color-scheme <scheme> emulate preferred color scheme, "light" or "dark" --device <deviceName> emulate device, for example "iPhone 11" --geolocation <coordinates> specify geolocation coordinates, for example "37.819722,-122.478611" --load-storage <filename> load context storage state from the file, previously saved with --save-storage --lang <language> specify language / locale, for example "en-GB" --proxy-server <proxy> specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080" --save-storage <filename> save context storage state at the end, for later use with --load-storage --timezone <time zone> time zone to emulate, for example "Europe/Rome" --timeout <timeout> timeout for Playwright actions in milliseconds (default: "10000") --user-agent <ua string> specify user agent string --viewport-size <size> specify browser viewport size in pixels, for example "1280, 720" -h, --help display help for command
with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto('https://spa6.scrape.center/') page.wait_for_load_state('networkidle') elements = page.query_selector_all('a.name') for element in elements: print(element.get_attribute('href')) print(element.text_content()) browser.close()
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx 霸王别姬 - Farewell My Concubine /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy 这个杀手不太冷 - Léon /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIz 肖申克的救赎 - The Shawshank Redemption /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI0 泰坦尼克号 - Titanic /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI1 罗马假日 - Roman Holiday /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2 唐伯虎点秋香 - Flirting Scholar /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3 乱世佳人 - Gone with the Wind /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI4 喜剧之王 - The King of Comedy /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5 楚门的世界 - The Truman Show /detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA== 狮子王 - The Lion King